├── .gitignore ├── setup.cfg ├── MANIFEST.in ├── tests ├── testrand.py ├── testpbkdf.py ├── testmac.py ├── testec.py ├── testoids.py ├── testcipher.py ├── testbio.py ├── testdigest.py ├── testpkey.py └── testx509.py ├── LICENSE ├── ctypescrypto ├── __init__.py ├── pbkdf2.py ├── exception.py ├── rand.py ├── engine.py ├── ec.py ├── mac.py ├── bio.py ├── oid.py ├── digest.py ├── cipher.py ├── cms.py ├── pkey.py └── x509.py ├── setup.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | core 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include tests/*.py 2 | include LICENSE 3 | include README.md 4 | include MANIFEST 5 | include MANIFEST.in 6 | -------------------------------------------------------------------------------- /tests/testrand.py: -------------------------------------------------------------------------------- 1 | from ctypescrypto.rand import * 2 | import unittest 3 | 4 | class TestRand(unittest.TestCase): 5 | def test_bytes(self): 6 | b=bytes(100) 7 | self.assertEqual(len(b),100) 8 | b2=bytes(100) 9 | self.assertNotEqual(b,b2) 10 | def test_pseudo_bytes(self): 11 | b=pseudo_bytes(100) 12 | self.assertEqual(len(b),100) 13 | b2=pseudo_bytes(100) 14 | self.assertNotEqual(b,b2) 15 | def test_seed(self): 16 | b=b"aaqwrwfsagdsgdsfgdsfgdfsgdsfgdsgfdsfgdsfg" 17 | seed(b) 18 | # Check if no segfault here 19 | def test_entropy(self): 20 | b=b"aaqwrwfsagdsgdsfgdsfgdfsgdsfgdsgfdsfgdsfg" 21 | seed(b,2.25) 22 | # Check if no segfault here 23 | def test_Status(self): 24 | i=status() 25 | self.assertEqual(i,1) 26 | 27 | if __name__ == '__main__': 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Victor Wagner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/testpbkdf.py: -------------------------------------------------------------------------------- 1 | from ctypescrypto.pbkdf2 import pbkdf2 2 | import unittest 3 | 4 | class TestPBKDF2(unittest.TestCase): 5 | answersha1=b'\xc13\xb3\xc8\x80\xc2\t\x01\xdaR]\x08\x03\xaf>\x85\xed\x9bU\xf0\x89\n\x81Ctu\xee\xe3\xfe\xd9\xfd\x85\xe2"\x8c\xfbQ\xfeb4\x8f(ZF\xfd\xc3w\x13' 6 | answersha256=b'oY\xaf\xf7\xfeB7@\xa80%\t\'\xd5r0\xbe\xb4\xf7\xe6TQ\xd2|Tx\xc0e\xff[0a\xe56\xec\xff\xda\xcd\xed~\xbde\xad"\xe8\t\x01o' 7 | answersha1_1000=b'\xe9\xfe\xbf\xf5K\xfc\xe6h\xfd\xe3\x01\xac\xc8Uc\xcc\x9d\xc7\x1e\xf6\xf8\xd7\xaa\xef\x06se\xbe\x0e^e"\xefa\xba\xe1\xb0\x0b\xc1;\xcd\x05G<\xcc\rE\xfb' 8 | def test_defaults(self): 9 | d=pbkdf2("password",b"saltsalt",48) 10 | self.assertEqual(d,self.answersha1) 11 | def test_sha1(self): 12 | d=pbkdf2("password",b"saltsalt",48,digesttype="sha1",iterations=2000) 13 | self.assertEqual(d,self.answersha1) 14 | def test_1000iter(self): 15 | d=pbkdf2("password",b"saltsalt",48,digesttype="sha1",iterations=1000) 16 | self.assertEqual(d,self.answersha1_1000) 17 | def test_sha256(self): 18 | d=pbkdf2("password",b"\01\02\03\04\0abc",48,digesttype="sha256") 19 | self.assertEqual(d,self.answersha256) 20 | 21 | if __name__ == "__main__": 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /ctypescrypto/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface to some libcrypto functions 3 | 4 | """ 5 | 6 | 7 | from ctypes import CDLL, c_char_p, c_void_p, c_long,c_uint64 8 | from ctypes.util import find_library 9 | import sys 10 | global strings_loaded 11 | 12 | def config(filename=None): 13 | """ 14 | Loads OpenSSL Config file. If none are specified, loads default 15 | (compiled in) one 16 | """ 17 | libcrypto.OPENSSL_config(filename) 18 | 19 | __all__ = ['config'] 20 | 21 | if sys.platform.startswith('win'): 22 | __libname__ = find_library('libeay32') 23 | else: 24 | __libname__ = find_library('crypto') 25 | 26 | if __libname__ is None: 27 | raise OSError("Cannot find OpenSSL crypto library") 28 | 29 | #__libname__ = "/usr/local/ssl/lib/libcrypto.so.1.1" 30 | 31 | libcrypto = CDLL(__libname__) 32 | libcrypto.OPENSSL_config.argtypes = (c_char_p, ) 33 | pyver=int(sys.version[0]) 34 | if pyver == 2: 35 | bintype = str 36 | chartype = unicode 37 | inttype = (int, long) 38 | else: 39 | bintype = bytes 40 | chartype = str 41 | inttype = int 42 | 43 | if hasattr(libcrypto,'OPENSSL_init_crypto'): 44 | libcrypto.OPENSSL_init_crypto.argtypes = (c_uint64,c_void_p) 45 | libcrypto.OPENSSL_init_crypto(2+4+8+0x40,None) 46 | strings_loaded = True 47 | else: 48 | libcrypto.OPENSSL_add_all_algorithms_conf() 49 | strings_loaded = False 50 | -------------------------------------------------------------------------------- /ctypescrypto/pbkdf2.py: -------------------------------------------------------------------------------- 1 | """ 2 | PKCS5 PBKDF2 function. 3 | 4 | """ 5 | 6 | from ctypes import c_char_p, c_int, c_void_p, create_string_buffer 7 | from ctypescrypto import libcrypto, chartype 8 | from ctypescrypto.digest import DigestType 9 | from ctypescrypto.exception import LibCryptoError 10 | 11 | __all__ = ['pbkdf2'] 12 | 13 | def pbkdf2(password, salt, outlen, digesttype="sha1", iterations=2000): 14 | """ 15 | Interface to PKCS5_PBKDF2_HMAC function 16 | Parameters: 17 | 18 | @param password - password to derive key from 19 | @param salt - random salt to use for key derivation 20 | @param outlen - number of bytes to derive 21 | @param digesttype - name of digest to use to use (default sha1) 22 | @param iterations - number of iterations to use 23 | 24 | @returns outlen bytes of key material derived from password and salt 25 | """ 26 | dgst = DigestType(digesttype) 27 | out = create_string_buffer(outlen) 28 | if isinstance(password,chartype): 29 | pwd = password.encode("utf-8") 30 | else: 31 | pwd = password 32 | res = libcrypto.PKCS5_PBKDF2_HMAC(pwd, len(pwd), salt, len(salt), 33 | iterations, dgst.digest, outlen, out) 34 | if res <= 0: 35 | raise LibCryptoError("error computing PBKDF2") 36 | return out.raw 37 | 38 | libcrypto.PKCS5_PBKDF2_HMAC.argtypes = (c_char_p, c_int, c_char_p, c_int, c_int, 39 | c_void_p, c_int, c_char_p) 40 | libcrypto.PKCS5_PBKDF2_HMAC.restupe = c_int 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | import distutils.cmd 3 | import sys, os 4 | 5 | class MyTests(distutils.cmd.Command): 6 | user_options = [] 7 | def initialize_options(self): 8 | pass 9 | def finalize_options(self): 10 | pass 11 | def run(self): 12 | sys.path.insert(0, os.getcwd()) 13 | import unittest 14 | result = unittest.TextTestResult(sys.stdout, True, True) 15 | suite = unittest.defaultTestLoader.discover("./tests") 16 | print ("Discovered %d test cases" % suite.countTestCases()) 17 | result.buffer = True 18 | suite.run(result) 19 | print ("") 20 | if not result.wasSuccessful(): 21 | if len(result.errors): 22 | print ("============ Errors disovered =================") 23 | for res in result.errors: 24 | print (res[0], ":", res[1]) 25 | 26 | if len(result.failures): 27 | print ("============ Failures disovered =================") 28 | for res in result.failures: 29 | print (res[0], ":", res[1]) 30 | sys.exit(1) 31 | else: 32 | print ("All tests successful") 33 | 34 | setup( 35 | name="ctypescrypto", 36 | version="0.5", 37 | description="CTypes-based interface for some OpenSSL libcrypto features", 38 | author="Victor Wagner", 39 | author_email="vitus@wagner.pp.ru", 40 | url="https://github.com/vbwagner/ctypescrypto", 41 | packages=["ctypescrypto"], 42 | cmdclass={"test":MyTests} 43 | ) 44 | 45 | -------------------------------------------------------------------------------- /ctypescrypto/exception.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exception which extracts libcrypto error information 3 | """ 4 | from ctypes import c_ulong, c_char_p, create_string_buffer 5 | from ctypescrypto import libcrypto, strings_loaded, pyver 6 | 7 | __all__ = ['LibCryptoError', 'clear_err_stack'] 8 | 9 | if pyver == 2: 10 | def _get_error_str(err_code,buf): 11 | return libcrypto.ERR_error_string(err_code,buf) 12 | else: 13 | def _get_error_str(err_code,buf): 14 | return libcrypto.ERR_error_string(err_code,buf).decode('utf-8') 15 | class LibCryptoError(Exception): 16 | """ 17 | Exception for libcrypto errors. Adds all the info, which can be 18 | extracted from internal (per-thread) libcrypto error stack to the message, 19 | passed to the constructor. 20 | """ 21 | def __init__(self, msg): 22 | global strings_loaded 23 | if not strings_loaded: 24 | libcrypto.ERR_load_crypto_strings() 25 | strings_loaded = True 26 | err_code = libcrypto.ERR_get_error() 27 | mesg = msg 28 | buf = create_string_buffer(128) 29 | while err_code != 0: 30 | mesg += "\n\t" + _get_error_str(err_code, buf) 31 | err_code = libcrypto.ERR_get_error() 32 | super(LibCryptoError, self).__init__(mesg) 33 | 34 | def clear_err_stack(): 35 | """ 36 | Clears internal libcrypto err stack. Call it if you've checked 37 | return code and processed exceptional situation, so subsequent 38 | raising of the LibCryptoError wouldn't list already handled errors 39 | """ 40 | libcrypto.ERR_clear_error() 41 | 42 | libcrypto.ERR_get_error.restype = c_ulong 43 | libcrypto.ERR_error_string.restype = c_char_p 44 | libcrypto.ERR_error_string.argtypes = (c_ulong, c_char_p) 45 | -------------------------------------------------------------------------------- /tests/testmac.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | from ctypescrypto.oid import Oid 3 | from base64 import b16decode,b16encode 4 | from ctypescrypto.mac import * 5 | from ctypescrypto.engine import set_default 6 | import unittest 7 | 8 | class TestMac(unittest.TestCase): 9 | def test_hmac_default(self): 10 | d=MAC('hmac',key=b'1234'*4) 11 | d.update(b'The Quick brown fox jumps over the lazy dog\n') 12 | self.assertEqual(d.name,'hmac-md5') 13 | self.assertEqual(d.hexdigest(),'A9C16D91CDF2A99273B72336D0D16B56') 14 | def test_hmac_digestdataa(self): 15 | d=MAC('hmac',key=b'1234'*4) 16 | h=d.hexdigest(b'The Quick brown fox jumps over the lazy dog\n') 17 | self.assertEqual(d.name,'hmac-md5') 18 | self.assertEqual(h,'A9C16D91CDF2A99273B72336D0D16B56') 19 | def test_hmac_byoid(self): 20 | d=MAC(Oid('hmac'),key=b'1234'*4) 21 | d.update(b'The Quick brown fox jumps over the lazy dog\n') 22 | self.assertEqual(d.name,'hmac-md5') 23 | self.assertEqual(d.hexdigest(),'A9C16D91CDF2A99273B72336D0D16B56') 24 | def test_mac_wrongtype(self): 25 | with self.assertRaises(TypeError): 26 | d=MAC(Oid('hmac').nid,key=b'1234'*4) 27 | def test_hmac_sha256(self): 28 | d=MAC('hmac',key=b'1234'*16,digest='sha256') 29 | d.update(b'The Quick brown fox jumps over the lazy dog\n') 30 | self.assertEqual(d.name,'hmac-sha256') 31 | self.assertEqual(d.hexdigest(),'BEBA086E1C67200664DCDEEC697D99DB1A8DAA72933A36B708FC5FD568173095') 32 | def test_gostmac(self): 33 | set_default('gost') 34 | d=MAC('gost-mac',key=b'1234'*8) 35 | d.update(b'The Quick brown fox jumps over the lazy dog\n') 36 | self.assertEqual(d.name,'gost-mac') 37 | self.assertEqual(d.digest_size,4) 38 | self.assertEqual(d.hexdigest(),'76F25AE3') 39 | with self.assertRaisesRegexp(DigestError,"invalid mac key length"): 40 | d=MAC('gost-mac',key=b'1234'*4) 41 | 42 | if __name__ == "__main__": 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /tests/testec.py: -------------------------------------------------------------------------------- 1 | from ctypescrypto import pyver 2 | from ctypescrypto.oid import Oid 3 | from ctypescrypto.ec import create 4 | from base64 import b16decode 5 | from subprocess import Popen, PIPE 6 | import unittest 7 | 8 | def dump_key(key): 9 | """ Convert key into printable form using openssl utility 10 | Used to compare keys which can be stored in different 11 | format by different OpenSSL versions 12 | """ 13 | return Popen(["openssl","pkey","-text","-noout"],stdin=PIPE,stdout=PIPE).communicate(key)[0] 14 | 15 | def dump_pub_key(key): 16 | """ Convert key into printable form using openssl utility 17 | Used to compare keys which can be stored in different 18 | format by different OpenSSL versions 19 | """ 20 | return Popen(["openssl","pkey","-text_pub","-noout"],stdin=PIPE,stdout=PIPE).communicate(key)[0] 21 | class TestEcCreation(unittest.TestCase): 22 | ec1priv=b"""-----BEGIN PRIVATE KEY----- 23 | MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgKnG6neqZvB98EEuuxnHs 24 | fv+L/5abuNNG20wzUqRpncOhRANCAARWKXWeUZ6WiCKZ2kHx87jmJyx0G3ZB1iQC 25 | +Gp2AJYswbQPhGPigKolzIbZYfwnn7QOca6N8QDhPAn3QQK8trZI 26 | -----END PRIVATE KEY----- 27 | """ 28 | bigkey=b"""-----BEGIN PRIVATE KEY----- 29 | MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgAAAAAAAAAAAAAAAAAAAA 30 | AUVRIxlQt1/EQC2hcy/Jvr6hRANCAASRZsKJufkF5V+ePfn2nX81a0oiCV+JT0cV 31 | cUqktWYGr/GB65Zr5Ky1z/nha2bYCb6U4hTwbJP9CRCZr5hJklXn 32 | -----END PRIVATE KEY----- 33 | """ 34 | def test_keyone(self): 35 | key=create(Oid("secp256k1"),b16decode("2A71BA9DEA99BC1F7C104BAEC671EC7EFF8BFF969BB8D346DB4C3352A4699DC3",True)) 36 | 37 | out=key.exportpriv() 38 | if pyver > 2: 39 | out=out.encode("ascii") 40 | self.assertEqual(dump_key(out),dump_key(self.ec1priv)) 41 | if pyver == 2: 42 | self.assertEqual(str(key),dump_pub_key(self.ec1priv)) 43 | else: 44 | self.assertEqual(str(key).encode("ascii"),dump_pub_key(self.ec1priv)) 45 | 46 | def test_bignum(self): 47 | keyval=b'\xff'*32 48 | key=create(Oid("secp256k1"),keyval) 49 | keyblob = key.exportpriv() 50 | if pyver > 2: 51 | keyblob = keyblob.encode("ascii") 52 | self.assertEqual(dump_key(keyblob),dump_key(self.bigkey)) 53 | keyblob2 = str(key) 54 | if pyver > 2: 55 | keyblob2 = keyblob2.encode('ascii') 56 | self.assertEqual(keyblob2,dump_pub_key(self.bigkey)) 57 | if __name__ == "__main__": 58 | unittest.main() 59 | -------------------------------------------------------------------------------- /ctypescrypto/rand.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface to the OpenSSL pseudo-random generator 3 | """ 4 | 5 | from ctypes import create_string_buffer, c_char_p, c_int, c_double 6 | from ctypescrypto import libcrypto, bintype 7 | from ctypescrypto.exception import LibCryptoError 8 | 9 | __all__ = ['RandError', 'bytes', 'pseudo_bytes', 'seed', 'status'] 10 | 11 | class RandError(LibCryptoError): 12 | """ Exception raised when openssl function return error """ 13 | pass 14 | 15 | def bytes(num, check_result=False): 16 | """ 17 | Returns num bytes of cryptographically strong pseudo-random 18 | bytes. If checkc_result is True, raises error if PRNG is not 19 | seeded enough 20 | """ 21 | 22 | if num <= 0: 23 | raise ValueError("'num' should be > 0") 24 | buf = create_string_buffer(num) 25 | result = libcrypto.RAND_bytes(buf, num) 26 | if check_result and result == 0: 27 | raise RandError("Random Number Generator not seeded sufficiently") 28 | return buf.raw[:num] 29 | 30 | def pseudo_bytes(num): 31 | """ 32 | Returns num bytes of pseudo random data. Pseudo- random byte 33 | sequences generated by pseudo_bytes() will be unique if 34 | they are of sufficient length, but are not necessarily 35 | unpredictable. They can be used for non-cryptographic purposes 36 | and for certain purposes in cryptographic protocols, but usually 37 | not for key generation etc. 38 | """ 39 | if num <= 0: 40 | raise ValueError("'num' should be > 0") 41 | buf = create_string_buffer(num) 42 | libcrypto.RAND_pseudo_bytes(buf, num) 43 | return buf.raw[:num] 44 | 45 | def seed(data, entropy=None): 46 | """ 47 | Seeds random generator with data. 48 | If entropy is not None, it should be floating point(double) 49 | value estimating amount of entropy in the data (in bytes). 50 | """ 51 | if not isinstance(data, bintype): 52 | raise TypeError("A string is expected") 53 | ptr = c_char_p(data) 54 | size = len(data) 55 | if entropy is None: 56 | libcrypto.RAND_seed(ptr, size) 57 | else: 58 | libcrypto.RAND_add(ptr, size, entropy) 59 | 60 | def status(): 61 | """ 62 | Returns 1 if random generator is sufficiently seeded and 0 63 | otherwise 64 | """ 65 | 66 | return libcrypto.RAND_status() 67 | 68 | libcrypto.RAND_add.argtypes = (c_char_p, c_int, c_double) 69 | libcrypto.RAND_seed.argtypes = (c_char_p, c_int) 70 | libcrypto.RAND_pseudo_bytes.argtypes = (c_char_p, c_int) 71 | libcrypto.RAND_bytes.argtypes = (c_char_p, c_int) 72 | -------------------------------------------------------------------------------- /tests/testoids.py: -------------------------------------------------------------------------------- 1 | from ctypescrypto.oid import Oid,create,cleanup 2 | import unittest 3 | 4 | class TestStandard(unittest.TestCase): 5 | def test_cn(self): 6 | o=Oid("2.5.4.3") 7 | self.assertEqual(repr(o),"Oid('2.5.4.3')") 8 | self.assertEqual(o.dotted(),"2.5.4.3") 9 | self.assertEqual(str(o),"2.5.4.3") 10 | self.assertEqual(o.shortname(),"CN") 11 | self.assertEqual(o.longname(),"commonName") 12 | def test_getnid(self): 13 | o=Oid("2.5.4.3") 14 | x=Oid("CN") 15 | self.assertEqual(o.nid,x.nid) 16 | self.assertEqual(o,x) 17 | self.assertEqual(hash(o),hash(x)) 18 | 19 | def test_cons2(self): 20 | o=Oid("2.5.4.3") 21 | x=Oid("commonName") 22 | self.assertEqual(o.nid,x.nid) 23 | def test_bynid(self): 24 | o=Oid("2.5.4.3") 25 | x=Oid(o.nid) 26 | self.assertEqual(o.nid,x.nid) 27 | def test_clone(self): 28 | o1=Oid('2.5.4.3') 29 | o2=Oid(o1) 30 | self.assertEqual(o1.nid,o2.nid) 31 | def test_fromunicode(self): 32 | o=Oid(u'commonName') 33 | self.assertEqual(o.shortname(),'CN') 34 | def test_wrongoid(self): 35 | with self.assertRaises(ValueError): 36 | o=Oid("1.2.3.4.5.6.7.8.10.111.1111") 37 | def test_wrongname(self): 38 | with self.assertRaises(ValueError): 39 | o=Oid("No such oid in the database") 40 | def test_wrongnid(self): 41 | with self.assertRaises(ValueError): 42 | o=Oid(9999999) 43 | def test_wrongtype(self): 44 | with self.assertRaises(TypeError): 45 | o=Oid([2,5,3,4]) 46 | 47 | class TestCustom(unittest.TestCase): 48 | def _no_testCreate(self): 49 | d='1.2.643.9.100.99' 50 | sn="CtypesCryptoTestOid" 51 | long_name="Test Oid in CryptoCom namespace" 52 | o=create(d,sn,long_name) 53 | self.assertEqual(str(o),d) 54 | self.assertEqual(o.shortname(),sn) 55 | self.assertEqual(o.longname(),long_name) 56 | def testLookup(self): 57 | d='1.2.643.9.100.99' 58 | sn="CtypesCryptoTestOid" 59 | long_name="Test Oid In CryptoCom Namespace" 60 | o=create(d,sn,long_name) 61 | x=Oid(sn) 62 | self.assertEqual(o,x) 63 | def _no_testFromObj(self): 64 | from ctypescrypto import libcrypto 65 | from ctypes import c_int, c_char_p, c_void_p 66 | libcrypto.OBJ_txt2obj.argtypes = (c_char_p, c_int) 67 | libcrypto.OBJ_txt2obj.restype = c_void_p 68 | obj= libcrypto.OBJ_txt2obj("1.2.643.100.9",1) 69 | oid=Oid.fromobj(obj) 70 | self.assertEqual(str(oid),'1.2.643.100.9') 71 | def tearDown(self): 72 | # Always call cleanup before next test 73 | cleanup() 74 | 75 | 76 | 77 | 78 | if __name__ == '__main__': 79 | unittest.main() 80 | -------------------------------------------------------------------------------- /ctypescrypto/engine.py: -------------------------------------------------------------------------------- 1 | """ 2 | engine loading and configuration 3 | """ 4 | from ctypes import c_void_p, c_char_p, c_int 5 | from ctypescrypto import libcrypto,pyver 6 | from ctypescrypto.exception import LibCryptoError 7 | 8 | __all__ = ['default', 'set_default', 'Engine'] 9 | 10 | default = None 11 | 12 | class Engine(object): 13 | """ 14 | Represents Openssl loadable module (engine). 15 | Allows to create PKey objects from private keys stored 16 | in the token, accessed by engine 17 | """ 18 | def __init__(self, engine_id, **kwargs): 19 | if pyver > 2 or isinstance(engine_id, unicode): 20 | engine_id = engine_id.encode('utf-8') 21 | eng = libcrypto.ENGINE_by_id(engine_id) 22 | if eng is None: 23 | # Try load engine 24 | eng = libcrypto.ENGINE_by_id("dynamic") 25 | if eng is None: 26 | raise LibCryptoError("Cannot get 'dynamic' engine") 27 | if not libcrypto.ENGINE_ctrl_cmd_string(eng, "SO_PATH", 28 | engine_id, 0): 29 | raise LibCryptoError("Cannot execute ctrl cmd SO_PATH") 30 | if not libcrypto.ENGINE_ctrl_cmd_string(eng, "LOAD", None, 0): 31 | raise LibCryptoError("Cannot execute ctrl cmd LOAD") 32 | if eng is None: 33 | raise ValueError("Cannot find engine " + engine) 34 | for cmd, value in kwargs.items(): 35 | if not libcrypto.ENGINE_ctrl_cmd_string(eng, cmd, value, 0): 36 | raise LibCryptoError("Cannot execute ctrl cmd %s" % cmd) 37 | if not libcrypto.ENGINE_init(eng): 38 | raise LibCryptoError("Cannot initialize engine") 39 | self.ptr = eng 40 | 41 | def private_key(self, key_id, ui_method = None, ui_data=None): 42 | from ctypescrypto.pkey import PKey 43 | if ui_method is None: 44 | ui_ptr = libcrypto.UI_OpenSSL() 45 | else: 46 | ui_ptr = ui_method.ptr 47 | pkey = libcrypto.ENGINE_load_private_key(self.ptr, key_id, ui_ptr, 48 | ui_data) 49 | if pkey is None: 50 | raise LibCryptoError("Cannot load private key") 51 | return PKey(ptr=pkey, cansign=True) 52 | 53 | def set_default(eng, algorithms=0xFFFF): 54 | """ 55 | Sets specified engine as default for all 56 | algorithms, supported by it 57 | 58 | For compatibility with 0.2.x if string is passed instead 59 | of engine, attempts to load engine with this id 60 | """ 61 | if not isinstance(eng,Engine): 62 | eng=Engine(eng) 63 | global default 64 | libcrypto.ENGINE_set_default(eng.ptr, c_int(algorithms)) 65 | default = eng 66 | 67 | # Declare function result and arguments for used functions 68 | libcrypto.ENGINE_by_id.restype = c_void_p 69 | libcrypto.ENGINE_by_id.argtypes = (c_char_p, ) 70 | libcrypto.ENGINE_set_default.argtypes = (c_void_p, c_int) 71 | libcrypto.ENGINE_ctrl_cmd_string.argtypes = (c_void_p, c_char_p, c_char_p, 72 | c_int) 73 | libcrypto.ENGINE_finish.argtypes = (c_char_p, ) 74 | libcrypto.ENGINE_init.argtypes = (c_void_p, ) 75 | libcrypto.UI_OpenSSL.restype = c_void_p 76 | libcrypto.ENGINE_load_private_key.argtypes = (c_void_p, c_char_p, c_void_p, c_void_p) 77 | libcrypto.ENGINE_load_private_key.restype = c_void_p 78 | -------------------------------------------------------------------------------- /ctypescrypto/ec.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for EC keypair operation missing form public libcrypto API 3 | """ 4 | from ctypescrypto.pkey import PKey, PKeyError 5 | from ctypes import c_void_p, c_char_p, c_int, byref, POINTER 6 | from ctypescrypto import libcrypto 7 | 8 | __all__ = ['create'] 9 | 10 | def create(curve, data): 11 | """ 12 | Creates EC keypair from the just secret key and curve name 13 | 14 | @param curve - name of elliptic curve 15 | @param num - byte array or long number representing key 16 | """ 17 | ec_key = libcrypto.EC_KEY_new_by_curve_name(curve.nid) 18 | if ec_key is None: 19 | raise PKeyError("EC_KEY_new_by_curvename") 20 | group = libcrypto.EC_KEY_get0_group(ec_key) 21 | if group is None: 22 | raise PKeyError("EC_KEY_get0_group") 23 | libcrypto.EC_GROUP_set_asn1_flag(group, 1) 24 | raw_key = libcrypto.BN_new() 25 | if isinstance(data, int): 26 | libcrypto.BN_hex2bn(byref(raw_key), hex(data)) 27 | else: 28 | if raw_key is None: 29 | raise PKeyError("BN_new") 30 | if libcrypto.BN_bin2bn(data, len(data), raw_key) is None: 31 | raise PKeyError("BN_bin2bn") 32 | ctx = libcrypto.BN_CTX_new() 33 | if ctx is None: 34 | raise PKeyError("BN_CTX_new") 35 | order = libcrypto.BN_new() 36 | if order is None: 37 | raise PKeyError("BN_new") 38 | priv_key = libcrypto.BN_new() 39 | if priv_key is None: 40 | raise PKeyError("BN_new") 41 | if libcrypto.EC_GROUP_get_order(group, order, ctx) <= 0: 42 | raise PKeyError("EC_GROUP_get_order") 43 | if libcrypto.BN_nnmod(priv_key, raw_key, order, ctx) <= 0: 44 | raise PKeyError("BN_nnmod") 45 | if libcrypto.EC_KEY_set_private_key(ec_key, priv_key) <= 0: 46 | raise PKeyError("EC_KEY_set_private_key") 47 | pub_key = libcrypto.EC_POINT_new(group) 48 | if pub_key is None: 49 | raise PKeyError("EC_POINT_new") 50 | if libcrypto.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx) <= 0: 51 | raise PKeyError("EC_POINT_mul") 52 | if libcrypto.EC_KEY_set_public_key(ec_key, pub_key) <= 0: 53 | raise PKeyError("EC_KEY_set_public_key") 54 | libcrypto.BN_free(raw_key) 55 | libcrypto.BN_free(order) 56 | libcrypto.BN_free(priv_key) 57 | libcrypto.BN_CTX_free(ctx) 58 | pkey = libcrypto.EVP_PKEY_new() 59 | if pkey is None: 60 | raise PKeyError("EVP_PKEY_new") 61 | if libcrypto.EVP_PKEY_set1_EC_KEY(pkey, ec_key) <= 0: 62 | raise PKeyError("EVP_PKEY_set1_EC_KEY") 63 | libcrypto.EC_KEY_free(ec_key) 64 | return PKey(ptr=pkey, cansign=True) 65 | 66 | libcrypto.EVP_PKEY_new.restype = c_void_p 67 | libcrypto.EC_KEY_new_by_curve_name.restype = c_void_p 68 | libcrypto.EC_KEY_new_by_curve_name.argtypes = (c_int,) 69 | libcrypto.BN_new.restype = c_void_p 70 | libcrypto.BN_free.argtypes = (c_void_p, ) 71 | libcrypto.BN_hex2bn.argtypes = (POINTER(c_void_p), c_char_p) 72 | libcrypto.BN_bin2bn.argtypes = ( c_char_p, c_int, c_void_p) 73 | libcrypto.BN_bin2bn.restype = c_void_p 74 | libcrypto.BN_CTX_new.restype = c_void_p 75 | libcrypto.BN_CTX_free.argtypes = (c_void_p, ) 76 | libcrypto.BN_bin2bn.argtypes = (c_char_p, c_int, c_void_p) 77 | libcrypto.BN_nnmod.restype = c_int 78 | libcrypto.BN_nnmod.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p) 79 | libcrypto.EC_KEY_set_private_key.argtypes = (c_void_p, c_void_p) 80 | libcrypto.EC_POINT_new.argtypes = (c_void_p, ) 81 | libcrypto.EC_POINT_new.restype = c_void_p 82 | libcrypto.EC_POINT_mul.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p, 83 | c_void_p, c_void_p) 84 | libcrypto.EC_KEY_set_public_key.argtypes = (c_void_p, c_void_p) 85 | libcrypto.EC_KEY_get0_group.restype = c_void_p 86 | libcrypto.EC_KEY_get0_group.argtypes = (c_void_p,) 87 | libcrypto.EC_KEY_free.argtypes=(c_void_p,) 88 | libcrypto.EVP_PKEY_set1_EC_KEY.argtypes = (c_void_p, c_void_p) 89 | libcrypto.EC_GROUP_set_asn1_flag.argtypes = (c_void_p, c_int) 90 | libcrypto.EC_GROUP_get_order.restype = c_int 91 | libcrypto.EC_GROUP_get_order.argtypes = (c_void_p, c_void_p, c_void_p) 92 | -------------------------------------------------------------------------------- /tests/testcipher.py: -------------------------------------------------------------------------------- 1 | from ctypescrypto.oid import Oid 2 | from ctypescrypto import cipher 3 | import unittest 4 | import sys 5 | 6 | 7 | class TestCipherType(unittest.TestCase): 8 | def test_ciphdescbc(self): 9 | ct=cipher.CipherType("des-cbc") 10 | self.assertEqual(ct.block_size(),8) 11 | self.assertEqual(ct.key_length(),8) 12 | self.assertEqual(ct.iv_length(),8) 13 | self.assertEqual(ct.algo(),'DES-CBC') 14 | self.assertEqual(ct.oid().shortname(),"DES-CBC") 15 | self.assertEqual(ct.mode(),"CBC") 16 | def test_ciphaesofb(self): 17 | ct=cipher.CipherType("aes-256-ofb") 18 | self.assertEqual(ct.block_size(),1) 19 | self.assertEqual(ct.key_length(),32) 20 | self.assertEqual(ct.iv_length(),16) 21 | self.assertEqual(ct.algo(),'AES-256-OFB') 22 | self.assertEqual(ct.oid().shortname(),"AES-256-OFB") 23 | self.assertEqual(ct.mode(),"OFB") 24 | def test_unknowncipher(self): 25 | with self.assertRaises(cipher.CipherError): 26 | ct=cipher.CipherType("no-such-cipher") 27 | 28 | class TestEncryptDecrypt(unittest.TestCase): 29 | def test_cons_nokey(self): 30 | ct=cipher.CipherType("DES-CBC") 31 | with self.assertRaises(ValueError): 32 | c=cipher.Cipher(ct,None,None) 33 | def test_blockcipher(self): 34 | data=b"sdfdsddf" 35 | key=b'abcdabcd' 36 | c=cipher.new("DES-CBC",key) 37 | enc=c.update(data)+c.finish() 38 | # See if padding is added by default 39 | self.assertEqual(len(enc),16) 40 | d=cipher.new("DES-CBC",key,encrypt=False) 41 | dec=d.update(enc)+d.finish() 42 | self.assertEqual(data,dec) 43 | def test_blockcipher_nopadding(self): 44 | data=b"sdfdsddf" 45 | key=b'abcdabcd' 46 | c=cipher.new("DES-CBC",key) 47 | c.padding(False) 48 | enc=c.update(data)+c.finish() 49 | # See if padding is added by default 50 | self.assertEqual(len(enc),8) 51 | d=cipher.new("DES-CBC",key,encrypt=False) 52 | d.padding(False) 53 | dec=d.update(enc)+d.finish() 54 | self.assertEqual(data,dec) 55 | def test_ofb_cipher(self): 56 | data=b"sdfdsddfxx" 57 | key=b'abcdabcd' 58 | iv=b'abcdabcd' 59 | c=cipher.new("DES-OFB",key,iv=iv) 60 | enc=c.update(data)+c.finish() 61 | # See if padding is added by default 62 | self.assertEqual(len(enc),len(data)) 63 | d=cipher.new("DES-OFB",key,encrypt=False,iv=iv) 64 | dec=d.update(enc)+d.finish() 65 | self.assertEqual(data,dec) 66 | 67 | def test_ofb_noiv(self): 68 | data=b"sdfdsddfxx" 69 | encryptkey=b'abcdabcd'*4 70 | decryptkey=encryptkey[0:5]+encryptkey[5:] 71 | 72 | 73 | c=cipher.new("AES-256-OFB",encryptkey) 74 | enc=c.update(data)+c.finish() 75 | # See if padding is added by default 76 | self.assertEqual(len(enc),len(data)) 77 | d=cipher.new("AES-256-OFB",decryptkey,encrypt=False) 78 | dec=d.update(enc)+d.finish() 79 | self.assertEqual(data,dec) 80 | def test_wrong_keylength(self): 81 | data=b"sdfsdfxxx" 82 | key=b"abcdabcd" 83 | with self.assertRaises(ValueError): 84 | c=cipher.new("AES-128-OFB",key) 85 | def test_wrong_ivlength(self): 86 | key=b"abcdabcdabcdabcd" 87 | iv=b"xxxxx" 88 | with self.assertRaises(ValueError): 89 | c=cipher.new("AES-128-OFB",key,iv=iv) 90 | def test_variable_keylength(self): 91 | encryptkey=b"abcdefabcdefghtlgvasdgdgsdgdsg" 92 | data=b"asdfsdfsdfsdfsdfsdfsdfsdf" 93 | iv=b"abcdefgh" 94 | c=cipher.new("bf-ofb",encryptkey,iv=iv) 95 | ciphertext=c.update(data)+c.finish() 96 | decryptkey=encryptkey[0:5]+encryptkey[5:] 97 | d=cipher.new("bf-ofb",decryptkey,encrypt=False,iv=iv) 98 | deciph=d.update(ciphertext)+d.finish() 99 | self.assertEqual(deciph,data) 100 | 101 | if __name__ == '__main__': 102 | unittest.main() 103 | -------------------------------------------------------------------------------- /ctypescrypto/mac.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | This module provides interface to OpenSSL MAC functions. 4 | 5 | It has not only HMAC support, but can support other types of MAC. 6 | 7 | """ 8 | 9 | from ctypescrypto.digest import Digest,DigestType,DigestError 10 | from ctypescrypto.oid import Oid 11 | from ctypescrypto import libcrypto 12 | from ctypes import c_int,c_char_p, c_void_p, c_size_t,POINTER,create_string_buffer,pointer 13 | 14 | __all__ = ['MAC','DigestError'] 15 | class MAC(Digest): 16 | """ 17 | This object represents MAC context. It is quite simular 18 | to digest algorithm. It is simular to hmac objects provided 19 | by standard library 20 | """ 21 | def __init__(self,algorithm,key,digest=None,**kwargs): 22 | """ 23 | Constructor has to obligatory arguments: 24 | 25 | @param algorithm - which is name of MAC algorithm i.e 'hmac' or 26 | 'gost-mac' or equivalent Oid object 27 | @param key - byte buffer with key. 28 | 29 | Optional parameters are: 30 | digest - Oid or name of the digest algorithm to use. If none 31 | specified, OpenSSL will try to derive one from the MAC 32 | algorithm (or if algorithm is hmac, we'll substititute md5 33 | for compatibility with standard hmac module 34 | 35 | any other keyword argument is passed to EVP_PKEY_CTX as string 36 | option. 37 | 38 | """ 39 | if isinstance(algorithm,str): 40 | self.algorithm=Oid(algorithm) 41 | elif isinstance(algorithm,Oid): 42 | self.algorithm=algorithm 43 | else: 44 | raise TypeError("Algorthm must be string or Oid") 45 | if self.algorithm==Oid('hmac') and digest is None: 46 | digest='md5' 47 | self.name=self.algorithm.shortname().lower() 48 | if digest is not None: 49 | self.digest_type=DigestType(digest) 50 | self.name+='-'+self.digest_type.digest_name 51 | d=self.digest_type.digest 52 | else: 53 | self.digest_type=None 54 | d=None 55 | self.key=libcrypto.EVP_PKEY_new_mac_key(self.algorithm.nid,None,key,len(key)) 56 | if self.key is None: 57 | raise DigestError("EVP_PKEY_new_mac_key") 58 | pctx=c_void_p() 59 | self.ctx = self.newctx() 60 | if self.ctx == 0: 61 | raise DigestError("Unable to create digest context") 62 | if libcrypto.EVP_DigestSignInit(self.ctx,pointer(pctx),d,None,self.key) <= 0: 63 | raise DigestError("Unable to intialize digest context") 64 | self.digest_finalized=False 65 | if self.digest_type is None: 66 | self.digest_type=DigestType(Oid(libcrypto.EVP_MD_type(libcrypto.EVP_MD_CTX_md(self.ctx)))) 67 | for (name,val) in kwargs.items(): 68 | if libcrypto.EVP_PKEY_CTX_ctrl_str(pctx,name,val)<=0: 69 | raise DigestError("Unable to set mac parameter") 70 | self.digest_size = self.digest_type.digest_size 71 | self.block_size = self.digest_type.block_size 72 | def digest(self,data=None): 73 | """ 74 | Method digest is redefined to return keyed MAC value instead of 75 | just digest. 76 | """ 77 | if data is not None: 78 | self.update(data) 79 | b=create_string_buffer(256) 80 | size=c_size_t(256) 81 | if libcrypto.EVP_DigestSignFinal(self.ctx,b,pointer(size))<=0: 82 | raise DigestError('SignFinal') 83 | self.digest_finalized=True 84 | return b.raw[:size.value] 85 | 86 | libcrypto.EVP_DigestSignFinal.argtypes=(c_void_p,c_char_p,POINTER(c_size_t)) 87 | libcrypto.EVP_DigestSignFinal.restype=c_int 88 | libcrypto.EVP_DigestSignInit.argtypes=(c_void_p,POINTER(c_void_p),c_void_p,c_void_p,c_void_p) 89 | libcrypto.EVP_DigestSignInit.restype=c_int 90 | libcrypto.EVP_PKEY_CTX_ctrl_str.argtypes=(c_void_p,c_char_p,c_char_p) 91 | libcrypto.EVP_PKEY_CTX_ctrl_str.restype=c_int 92 | libcrypto.EVP_PKEY_new_mac_key.argtypes=(c_int,c_void_p,c_char_p,c_int) 93 | libcrypto.EVP_PKEY_new_mac_key.restype=c_void_p 94 | libcrypto.EVP_MD_CTX_md.argtypes=(c_void_p,) 95 | libcrypto.EVP_MD_CTX_md.restype=c_void_p 96 | -------------------------------------------------------------------------------- /tests/testbio.py: -------------------------------------------------------------------------------- 1 | from ctypescrypto.bio import Membio 2 | import unittest 3 | import sys 4 | if sys.version[0]>'2': 5 | def unicode(b): 6 | return str(b) 7 | 8 | 9 | class TestRead(unittest.TestCase): 10 | def test_readshortstr(self): 11 | s=b"A quick brown fox jumps over a lazy dog" 12 | bio=Membio(s) 13 | data=bio.read() 14 | self.assertEqual(data,s) 15 | data2=bio.read() 16 | self.assertEqual(data2,b"") 17 | del bio 18 | def test_readwithlen(self): 19 | s=b"A quick brown fox jumps over a lazy dog" 20 | bio=Membio(s) 21 | data=bio.read(len(s)) 22 | self.assertEqual(data,s) 23 | data2=bio.read(5) 24 | self.assertEqual(data2,b"") 25 | def test_readwrongtype(self): 26 | s=b"A quick brown fox jumps over a lazy dog" 27 | bio=Membio(s) 28 | with self.assertRaises(TypeError): 29 | data=bio.read("5") 30 | def test_reset(self): 31 | s=b"A quick brown fox jumps over a lazy dog" 32 | bio=Membio(s) 33 | data=bio.read() 34 | bio.reset() 35 | data2=bio.read() 36 | del bio 37 | self.assertEqual(data,data2) 38 | self.assertEqual(data,s) 39 | def test_readlongstr(self): 40 | poem=b'''Eyes of grey--a sodden quay, 41 | Driving rain and falling tears, 42 | As the steamer wears to sea 43 | In a parting storm of cheers. 44 | 45 | Sing, for Faith and Hope are high-- 46 | None so true as you and I-- 47 | Sing the Lovers' Litany: 48 | "Love like ours can never die!" 49 | 50 | Eyes of black--a throbbing keel, 51 | Milky foam to left and right; 52 | Whispered converse near the wheel 53 | In the brilliant tropic night. 54 | 55 | Cross that rules the Southern Sky! 56 | Stars that sweep and wheel and fly, 57 | Hear the Lovers' Litany: 58 | Love like ours can never die!" 59 | 60 | Eyes of brown--a dusty plain 61 | Split and parched with heat of June, 62 | Flying hoof and tightened rein, 63 | Hearts that beat the old, old tune. 64 | 65 | Side by side the horses fly, 66 | Frame we now the old reply 67 | Of the Lovers' Litany: 68 | "Love like ours can never die!" 69 | 70 | Eyes of blue--the Simla Hills 71 | Silvered with the moonlight hoar; 72 | Pleading of the waltz that thrills, 73 | Dies and echoes round Benmore. 74 | 75 | "Mabel," "Officers," "Goodbye," 76 | Glamour, wine, and witchery-- 77 | On my soul's sincerity, 78 | "Love like ours can never die!" 79 | 80 | Maidens of your charity, 81 | Pity my most luckless state. 82 | Four times Cupid's debtor I-- 83 | Bankrupt in quadruplicate. 84 | 85 | Yet, despite this evil case, 86 | And a maiden showed me grace, 87 | Four-and-forty times would I 88 | Sing the Lovers' Litany: 89 | "Love like ours can never die!"''' 90 | bio=Membio(poem) 91 | data=bio.read() 92 | self.assertEqual(data,poem) 93 | del bio 94 | def test_readparts(self): 95 | s=b"A quick brown fox jumps over the lazy dog" 96 | bio=Membio(s) 97 | a=bio.read(10) 98 | self.assertEqual(a,s[0:10]) 99 | b=bio.read(9) 100 | self.assertEqual(b,s[10:19]) 101 | c=bio.read() 102 | self.assertEqual(c,s[19:]) 103 | d=bio.read() 104 | self.assertEqual(d,b"") 105 | 106 | class TestWrite(unittest.TestCase): 107 | def test_write(self): 108 | b=Membio() 109 | b.write(b"A quick brown ") 110 | b.write(b"fox jumps over ") 111 | b.write(b"the lazy dog.") 112 | self.assertEqual(str(b),"A quick brown fox jumps over the lazy dog.") 113 | 114 | def test_unicode(self): 115 | b=Membio() 116 | s=b'\xd0\xba\xd0\xb0\xd0\xba \xd1\x8d\xd1\x82\xd0\xbe \xd0\xbf\xd0\xbe-\xd1\x80\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8' 117 | b.write(s) 118 | self.assertEqual(unicode(b),u'\u043a\u0430\u043a \u044d\u0442\u043e \u043f\u043e-\u0440\u0443\u0441\u0441\u043a\u0438') 119 | def test_unicode2(self): 120 | b=Membio() 121 | u=u'\u043a\u0430\u043a \u044d\u0442\u043e \u043f\u043e-\u0440\u0443\u0441\u0441\u043a\u0438' 122 | b.write(u) 123 | self.assertEqual(unicode(b),u) 124 | if __name__ == '__main__': 125 | unittest.main() 126 | -------------------------------------------------------------------------------- /ctypescrypto/bio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface to OpenSSL BIO library 3 | """ 4 | from ctypescrypto import libcrypto,pyver, inttype, chartype 5 | from ctypes import c_char_p, c_void_p, c_int, string_at, c_long 6 | from ctypes import POINTER, byref, create_string_buffer 7 | class Membio(object): 8 | """ 9 | Provides interface to OpenSSL memory bios 10 | use str() or unicode() to get contents of writable bio 11 | use bio member to pass to libcrypto function 12 | 13 | """ 14 | def __init__(self, data=None, clone=False): 15 | """ 16 | If data is specified, creates read-only BIO. 17 | If clone is True, makes copy of data in the instance member 18 | If data is 19 | None, creates writable BIO, contents of which can be retrieved 20 | by str() or unicode() 21 | 22 | """ 23 | if data is None: 24 | method = libcrypto.BIO_s_mem() 25 | self.bio = libcrypto.BIO_new(method) 26 | else: 27 | if isinstance(data, chartype): 28 | data = data.encode("utf-8") 29 | clone = True 30 | if clone : 31 | self.data = data 32 | self.bio = libcrypto.BIO_new_mem_buf(c_char_p(self.data), len(data)) 33 | else: 34 | self.bio = libcrypto.BIO_new_mem_buf(c_char_p(data), len(data)) 35 | 36 | def __del__(self): 37 | """ 38 | Cleans up memory used by bio 39 | """ 40 | if hasattr(self,'bio'): 41 | libcrypto.BIO_free(self.bio) 42 | del self.bio 43 | 44 | def __bytes__(self): 45 | """ 46 | Returns current contents of buffer as byte string 47 | """ 48 | string_ptr = c_char_p(None) 49 | string_len = libcrypto.BIO_ctrl(self.bio, 3, 0, byref(string_ptr)) 50 | return string_at(string_ptr, string_len) 51 | 52 | def __unicode__(self): 53 | """ 54 | Attempts to interpret current contents of buffer as UTF-8 string 55 | and convert it to unicode 56 | """ 57 | return self.__bytes__().decode("utf-8") 58 | if pyver == 2: 59 | __str__ = __bytes__ 60 | else: 61 | __str__ = __unicode__ 62 | def read(self, length=None): 63 | """ 64 | Reads data from readble BIO. For test purposes. 65 | @param length - if specifed, limits amount of data read. 66 | If not BIO is read until end of buffer 67 | """ 68 | if not length is None: 69 | if not isinstance(length, inttype) : 70 | raise TypeError("length to read should be number") 71 | buf = create_string_buffer(length) 72 | readbytes = libcrypto.BIO_read(self.bio, buf, length) 73 | if readbytes == -2: 74 | raise NotImplementedError("Function is not supported by" + 75 | "this BIO") 76 | if readbytes == -1: 77 | raise IOError 78 | if readbytes == 0: 79 | return b"" 80 | return buf.raw[:readbytes] 81 | else: 82 | buf = create_string_buffer(1024) 83 | out = b"" 84 | readbytes = 1 85 | while readbytes > 0: 86 | readbytes = libcrypto.BIO_read(self.bio, buf, 1024) 87 | if readbytes == -2: 88 | raise NotImplementedError("Function is not supported by " + 89 | "this BIO") 90 | if readbytes == -1: 91 | raise IOError 92 | if readbytes > 0: 93 | out += buf.raw[:readbytes] 94 | return out 95 | 96 | def write(self, data): 97 | """ 98 | Writes data to writable bio. For test purposes 99 | """ 100 | if pyver == 2: 101 | if isinstance(data, unicode): 102 | data = data.encode("utf-8") 103 | else: 104 | data = str(data) 105 | else: 106 | if not isinstance(data, bytes): 107 | data=str(data).encode("utf-8") 108 | 109 | written = libcrypto.BIO_write(self.bio, data, len(data)) 110 | if written == -2: 111 | raise NotImplementedError("Function not supported by this BIO") 112 | if written < len(data): 113 | raise IOError("Not all data were successfully written") 114 | 115 | def reset(self): 116 | """ 117 | Resets the read-only bio to start and discards all data from 118 | writable bio 119 | """ 120 | libcrypto.BIO_ctrl(self.bio, 1, 0, None) 121 | 122 | __all__ = ['Membio'] 123 | libcrypto.BIO_s_mem.restype = c_void_p 124 | libcrypto.BIO_new.restype = c_void_p 125 | libcrypto.BIO_new.argtypes = (c_void_p, ) 126 | libcrypto.BIO_ctrl.restype = c_long 127 | libcrypto.BIO_ctrl.argtypes = (c_void_p, c_int, c_long, POINTER(c_char_p)) 128 | libcrypto.BIO_read.argtypes = (c_void_p, c_char_p, c_int) 129 | libcrypto.BIO_write.argtypes = (c_void_p, c_char_p, c_int) 130 | libcrypto.BIO_free.argtypes = (c_void_p, ) 131 | libcrypto.BIO_new_mem_buf.restype = c_void_p 132 | libcrypto.BIO_new_mem_buf.argtypes = (c_char_p, c_int) 133 | -------------------------------------------------------------------------------- /ctypescrypto/oid.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface to OpenSSL object identifier database. 3 | 4 | It is primarily intended to deal with OIDs which are compiled into the 5 | database or defined in the openssl configuration files. 6 | 7 | But see create() function. 8 | 9 | OpenSSL maintains database of OIDs, which contain long and short 10 | human-readable names, which correspond to Oid as well as canonical 11 | dotted-decimal representation, and links it to small integer, named 12 | numeric identifier or 'nid'. Most OpenSSL functions which deals with 13 | ASN.1 structures such as certificates or cryptographic messages, 14 | expect or return nids, but it is very bad idea to hardcode nids into 15 | your app, because it can change after mere recompilation of OpenSSL 16 | library. 17 | 18 | This module provides Oid object which represents entry to OpenSSL 19 | OID database. 20 | """ 21 | from ctypescrypto import libcrypto, pyver,bintype,chartype,inttype 22 | from ctypes import c_char_p, c_void_p, c_int, create_string_buffer 23 | from ctypescrypto.exception import LibCryptoError 24 | 25 | __all__ = ['Oid', 'create', 'cleanup'] 26 | 27 | class Oid(object): 28 | """ 29 | Represents an OID (ASN.1 Object identifier). 30 | 31 | 32 | It can be consturucted by textual 33 | representation like Oid("commonName") or Oid("CN"), 34 | dotted-decimal Oid("1.2.3.4") or using OpenSSL numeric 35 | identifer (NID), which is typically returned or required by 36 | OpenSSL API functions. If object is consturcted from textual 37 | representation which is not present in the database, it fails 38 | with ValueError 39 | 40 | attribute nid - contains object nid. 41 | """ 42 | 43 | def __init__(self, value): 44 | """ 45 | Object constructor. Accepts string, integer, or another Oid 46 | object. 47 | 48 | Integer should be OpenSSL numeric identifier (nid) as returned 49 | by some libcrypto function or extracted from some libcrypto 50 | structure 51 | """ 52 | if isinstance(value, chartype): 53 | value = value.encode('ascii') 54 | if isinstance(value, bintype): 55 | self.nid = libcrypto.OBJ_txt2nid(value) 56 | if self.nid == 0: 57 | raise ValueError("Cannot find object %s in the database" % 58 | value) 59 | elif isinstance(value, inttype): 60 | short = libcrypto.OBJ_nid2sn(value) 61 | if short is None: 62 | raise ValueError("No such nid %d in the database" % value) 63 | self.nid = value 64 | elif isinstance(value, Oid): 65 | self.nid = value.nid 66 | else: 67 | raise TypeError("Cannot convert this type to object identifier") 68 | def __hash__(self): 69 | " Hash of object is equal to nid because Oids with same nid are same" 70 | return self.nid 71 | def __eq__ (self, other): 72 | return self.nid == other.nid 73 | def __hash__(self): 74 | """ Returns NID of object as hash value. Should make Oids with 75 | identical NID compare equal and also let use Oids as 76 | dictionary keys""" 77 | return self.nid 78 | def __str__(self): 79 | " Default string representation of Oid is dotted-decimal " 80 | return self.dotted() 81 | def __repr__(self): 82 | " Returns constructor call of Oid with dotted representation " 83 | return "Oid('%s')" % (self.dotted()) 84 | if pyver == 2: 85 | def shortname(self): 86 | " Returns short name if any " 87 | return libcrypto.OBJ_nid2sn(self.nid) 88 | def longname(self): 89 | " Returns long name if any " 90 | return libcrypto.OBJ_nid2ln(self.nid) 91 | else: 92 | def shortname(self): 93 | " Returns short name if any " 94 | return libcrypto.OBJ_nid2sn(self.nid).decode('utf-8') 95 | def longname(self): 96 | " Returns long name if any " 97 | return libcrypto.OBJ_nid2ln(self.nid).decode('utf-8') 98 | 99 | def dotted(self): 100 | " Returns dotted-decimal reperesentation " 101 | obj = libcrypto.OBJ_nid2obj(self.nid) 102 | buf = create_string_buffer(256) 103 | libcrypto.OBJ_obj2txt(buf, 256, obj, 1) 104 | if pyver == 2: 105 | return buf.value 106 | else: 107 | return buf.value.decode('ascii') 108 | @staticmethod 109 | def fromobj(obj): 110 | """ 111 | Creates an OID object from the pointer to ASN1_OBJECT c structure. 112 | This method intended for internal use for submodules which deal 113 | with libcrypto ASN1 parsing functions, such as x509 or CMS 114 | """ 115 | nid = libcrypto.OBJ_obj2nid(obj) 116 | if nid == 0: 117 | buf = create_string_buffer(80) 118 | dotted_len = libcrypto.OBJ_obj2txt(buf, 80, obj, 1) 119 | dotted = buf[:dotted_len] 120 | oid = create(dotted, dotted, dotted) 121 | else: 122 | oid = Oid(nid) 123 | return oid 124 | 125 | def create(dotted, shortname, longname): 126 | """ 127 | Creates new OID in the database 128 | 129 | @param dotted - dotted-decimal representation of new OID 130 | @param shortname - short name for new OID 131 | @param longname - long name for new OID 132 | 133 | @returns Oid object corresponding to new OID 134 | 135 | This function should be used with exreme care. Whenever 136 | possible, it is better to add new OIDs via OpenSSL configuration 137 | file 138 | 139 | Results of calling this function twice for same OIDor for 140 | Oid alredy in database are undefined 141 | 142 | """ 143 | if pyver > 2: 144 | dotted = dotted.encode('ascii') 145 | shortname = shortname.encode('utf-8') 146 | longname = longname.encode('utf-8') 147 | nid = libcrypto.OBJ_create(dotted, shortname, longname) 148 | if nid == 0: 149 | raise LibCryptoError("Problem adding new OID to the database") 150 | return Oid(nid) 151 | 152 | def cleanup(): 153 | """ 154 | Removes all the objects, dynamically added by current 155 | application from database. 156 | 157 | Note that in OpenSSL 1.1.0 and above OBJ_cleanup really does nothing 158 | """ 159 | if hasattr(libcrypto,"OBJ_cleanup"): 160 | libcrypto.OBJ_cleanup() 161 | 162 | libcrypto.OBJ_nid2sn.restype = c_char_p 163 | libcrypto.OBJ_nid2ln.restype = c_char_p 164 | libcrypto.OBJ_nid2obj.restype = c_void_p 165 | libcrypto.OBJ_obj2nid.restype = c_int 166 | libcrypto.OBJ_obj2txt.argtypes = (c_char_p, c_int, c_void_p, c_int) 167 | libcrypto.OBJ_txt2nid.argtupes = (c_char_p, ) 168 | libcrypto.OBJ_obj2nid.argtupes = (c_void_p, ) 169 | libcrypto.OBJ_create.argtypes = (c_char_p, c_char_p, c_char_p) 170 | -------------------------------------------------------------------------------- /tests/testdigest.py: -------------------------------------------------------------------------------- 1 | from ctypescrypto.oid import Oid 2 | from ctypescrypto import digest 3 | from base64 import b16decode,b16encode 4 | import unittest 5 | 6 | class TestDigestType(unittest.TestCase): 7 | def test_md4(self): 8 | d=digest.DigestType("md4") 9 | self.assertEqual(d.digest_size,16) 10 | self.assertEqual(d.block_size,64) 11 | self.assertEqual(d.oid,Oid("md4")) 12 | self.assertEqual(d.name,'md4') 13 | def test_md5(self): 14 | d=digest.DigestType("md5") 15 | self.assertEqual(d.digest_size,16) 16 | self.assertEqual(d.block_size,64) 17 | self.assertEqual(d.oid,Oid("md5")) 18 | self.assertEqual(d.name,'md5') 19 | def test_sha1(self): 20 | d=digest.DigestType("sha1") 21 | self.assertEqual(d.digest_size,20) 22 | self.assertEqual(d.block_size,64) 23 | self.assertEqual(d.oid,Oid("sha1")) 24 | self.assertEqual(d.name,'sha1') 25 | def test_sha256(self): 26 | d=digest.DigestType("sha256") 27 | self.assertEqual(d.digest_size,32) 28 | self.assertEqual(d.block_size,64) 29 | self.assertEqual(d.oid,Oid("sha256")) 30 | self.assertEqual(d.name,'sha256') 31 | def test_sha384(self): 32 | d=digest.DigestType("sha384") 33 | self.assertEqual(d.digest_size,48) 34 | self.assertEqual(d.block_size,128) 35 | self.assertEqual(d.oid,Oid("sha384")) 36 | self.assertEqual(d.name,'sha384') 37 | def test_sha512(self): 38 | d=digest.DigestType("sha512") 39 | self.assertEqual(d.digest_size,64) 40 | self.assertEqual(d.block_size,128) 41 | self.assertEqual(d.oid,Oid("sha512")) 42 | self.assertEqual(d.name,'sha512') 43 | def test_createfromoid(self): 44 | oid=Oid('sha256') 45 | d=digest.DigestType(oid) 46 | self.assertEqual(d.digest_size,32) 47 | self.assertEqual(d.block_size,64) 48 | self.assertEqual(d.oid,Oid("sha256")) 49 | self.assertEqual(d.name,'sha256') 50 | def test_createfromEVP_MD(self): 51 | d1=digest.DigestType("sha256") 52 | d2=digest.DigestType(None) 53 | with self.assertRaises(AttributeError): 54 | s=d2.name 55 | d2.digest=d1.digest 56 | self.assertEqual(d2.digest_size,32) 57 | self.assertEqual(d2.block_size,64) 58 | self.assertEqual(d2.oid,Oid("sha256")) 59 | self.assertEqual(d2.name,'sha256') 60 | def test_invalidDigest(self): 61 | with self.assertRaises(digest.DigestError): 62 | d=digest.DigestType("no-such-digest") 63 | 64 | 65 | class TestIface(unittest.TestCase): 66 | """ Test all methods with one algorithms """ 67 | msg=b"A quick brown fox jumps over the lazy dog." 68 | dgst="00CFFE7312BF9CA73584F24BDF7DF1D028340397" 69 | def test_cons(self): 70 | md=digest.DigestType("sha1") 71 | dgst=digest.Digest(md) 72 | dgst.update(self.msg) 73 | self.assertEqual(dgst.digest_size,20) 74 | self.assertEqual(dgst.hexdigest(),self.dgst) 75 | def test_digestwithdata(self): 76 | md=digest.DigestType("sha1") 77 | dgst=digest.Digest(md) 78 | self.assertEqual(dgst.digest(self.msg),b16decode(self.dgst)) 79 | def test_length(self): 80 | l=len(self.msg) 81 | msg=self.msg+b" Dog barks furiously." 82 | dgst=digest.new("sha1") 83 | dgst.update(msg,length=l) 84 | self.assertEqual(dgst.hexdigest(),self.dgst) 85 | def test_badlength(self): 86 | l=len(self.msg) 87 | dgst=digest.new("sha1") 88 | with self.assertRaises(ValueError): 89 | dgst.update(self.msg,length=l+1) 90 | def test_bindigest(self): 91 | dgst=digest.new("sha1") 92 | dgst.update(self.msg) 93 | self.assertEqual(dgst.digest_size,20) 94 | self.assertEqual(dgst.digest(),b16decode(self.dgst,True)) 95 | def test_duplicatedigest(self): 96 | dgst=digest.new("sha1") 97 | dgst.update(self.msg) 98 | v1=dgst.digest() 99 | v2=dgst.digest() 100 | self.assertEqual(v1,v2) 101 | def test_updatefinalized(self): 102 | dgst=digest.new("sha1") 103 | dgst.update(self.msg) 104 | h=dgst.hexdigest() 105 | with self.assertRaises(digest.DigestError): 106 | dgst.update(self.msg) 107 | def test_wrongtype(self): 108 | dgst=digest.new("sha1") 109 | with self.assertRaises(TypeError): 110 | dgst.update(['a','b','c']) 111 | with self.assertRaises(TypeError): 112 | dgst.update(18) 113 | with self.assertRaises(TypeError): 114 | dgst.update({"a":"b","c":"D"}) 115 | with self.assertRaises(TypeError): 116 | dgst.update(u'\u0430\u0431') 117 | def test_copy(self): 118 | dgst=digest.new("sha1") 119 | dgst.update(b"A quick brown fox jumps over ") 120 | d2=dgst.copy() 121 | dgst.update(b"the lazy dog.") 122 | value1=dgst.hexdigest() 123 | d2.update(b"the fat pig.") 124 | value2=d2.hexdigest() 125 | self.assertEqual(value1,"00CFFE7312BF9CA73584F24BDF7DF1D028340397") 126 | self.assertEqual(value2,"5328F33739BEC2A15B6A30F17D3BC13CC11A7C78") 127 | class TestAlgo(unittest.TestCase): 128 | """ Test all statdard algorithms """ 129 | def test_md5(self): 130 | d=digest.new("md5") 131 | self.assertEqual(d.digest_size,16) 132 | d.update(b"A quick brown fox jumps over the lazy dog.") 133 | self.assertEqual(d.hexdigest(),"DF756A3769FCAB0A261880957590C768") 134 | 135 | def test_md4(self): 136 | d=digest.new("md4") 137 | d.update(b"A quick brown fox jumps over the lazy dog.") 138 | self.assertEqual(d.digest_size,16) 139 | self.assertEqual(d.hexdigest(),"FAAED595A3E38BBF0D9B4B98021D200F") 140 | def test_sha256(self): 141 | d=digest.new("sha256") 142 | d.update(b"A quick brown fox jumps over the lazy dog.") 143 | self.assertEqual(d.digest_size,32) 144 | self.assertEqual(d.hexdigest(),"FFCA2587CFD4846E4CB975B503C9EB940F94566AA394E8BD571458B9DA5097D5") 145 | def test_sha384(self): 146 | d=digest.new("sha384") 147 | d.update(b"A quick brown fox jumps over the lazy dog.") 148 | self.assertEqual(d.digest_size,48) 149 | self.assertEqual(d.hexdigest(),"C7D71B1BA81D0DD028E79C7E75CF2F83169C14BA732CA5A2AD731151584E9DE843C1A314077D62B96B03367F72E126D8") 150 | def test_sha512(self): 151 | d=digest.new("sha512") 152 | self.assertEqual(d.digest_size,64) 153 | d.update(b"A quick brown fox jumps over the lazy dog.") 154 | self.assertEqual(d.hexdigest(),"3045575CF3B873DD656F5F3426E04A4ACD11950BB2538772EE14867002B408E21FF18EF7F7B2CAB484A3C1C0BE3F8ACC4AED536A427353C7748DC365FC1A8646") 155 | def test_wrongdigest(self): 156 | with self.assertRaises(digest.DigestError): 157 | dgst=digest.new("no-such-digest") 158 | 159 | if __name__ == "__main__": 160 | unittest.main() 161 | -------------------------------------------------------------------------------- /ctypescrypto/digest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements interface to OpenSSL EVP_Digest* functions. 3 | 4 | Interface made as close to hashlib as possible. 5 | 6 | This module is really an excess effort. Hashlib allows access to 7 | mostly same functionality except oids and nids of hashing 8 | algortithms (which might be needed for private key operations). 9 | 10 | hashlib even allows to use engine-provided digests if it is build 11 | with dinamically linked libcrypto - so use 12 | ctypescrypto.engine.set_default("gost",xFFFF) and md_gost94 13 | algorithm would be available both to this module and hashlib. 14 | 15 | """ 16 | from ctypes import c_int, c_char_p, c_void_p, POINTER, c_long, c_longlong 17 | from ctypes import create_string_buffer, byref 18 | from ctypescrypto import libcrypto,pyver, bintype 19 | from ctypescrypto.exception import LibCryptoError 20 | from ctypescrypto.oid import Oid 21 | DIGEST_ALGORITHMS = ("MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512") 22 | 23 | __all__ = ['DigestError', 'Digest', 'DigestType', 'new'] 24 | 25 | class DigestError(LibCryptoError): 26 | """ Exception raised if some OpenSSL function returns error """ 27 | pass 28 | 29 | def new(algname): 30 | """ 31 | Behaves just like hashlib.new. Creates digest object by 32 | algorithm name 33 | """ 34 | 35 | digest_type = DigestType(algname) 36 | return Digest(digest_type) 37 | 38 | class DigestType(object): 39 | """ 40 | Represents EVP_MD object - constant structure which describes 41 | digest algorithm 42 | """ 43 | def __init__(self, digest_name): 44 | """ 45 | Finds digest by its name. You can pass Oid object instead of 46 | name. 47 | 48 | Special case is when None is passed as name. In this case 49 | unitialized digest is created, and can be initalized later 50 | by setting its digest attribute to pointer to EVP_MD 51 | """ 52 | if digest_name is None: 53 | return 54 | 55 | if isinstance(digest_name, Oid): 56 | self.digest_name = digest_name.longname() 57 | else: 58 | self.digest_name = str(digest_name) 59 | self.digest = libcrypto.EVP_get_digestbyname(self.digest_name.encode('us-ascii')) 60 | if self.digest is None: 61 | raise DigestError("Unknown digest: %s" % self.digest_name) 62 | 63 | @property 64 | def name(self): 65 | """ Returns name of the digest """ 66 | if not hasattr(self, 'digest_name'): 67 | self.digest_name = Oid(libcrypto.EVP_MD_type(self.digest) 68 | ).longname() 69 | return self.digest_name 70 | 71 | def __del__(self): 72 | """ Empty destructor for constant object """ 73 | pass 74 | 75 | @property 76 | def digest_size(self): 77 | """ Returns size of digest """ 78 | return libcrypto.EVP_MD_size(self.digest) 79 | 80 | @property 81 | def block_size(self): 82 | """ Returns block size of the digest """ 83 | return libcrypto.EVP_MD_block_size(self.digest) 84 | 85 | @property 86 | def oid(self): 87 | """ Returns Oid object of digest type """ 88 | return Oid(libcrypto.EVP_MD_type(self.digest)) 89 | 90 | class Digest(object): 91 | """ 92 | Represents EVP_MD_CTX object which actually used to calculate 93 | digests. 94 | """ 95 | 96 | def __init__(self, digest_type): 97 | """ 98 | Initializes digest using given type. 99 | """ 100 | self.ctx = self.newctx() 101 | if self.ctx is None: 102 | raise DigestError("Unable to create digest context") 103 | self.digest_out = None 104 | self.digest_finalized = False 105 | result = libcrypto.EVP_DigestInit_ex(self.ctx, digest_type.digest, None) 106 | if result == 0: 107 | self._clean_ctx() 108 | raise DigestError("Unable to initialize digest") 109 | self.digest_type = digest_type 110 | self.digest_size = self.digest_type.digest_size 111 | self.block_size = self.digest_type.block_size 112 | 113 | def __del__(self): 114 | """ Uses _clean_ctx internal method """ 115 | self._clean_ctx() 116 | 117 | def update(self, data, length=None): 118 | """ 119 | Hashes given byte string 120 | 121 | @param data - string to hash 122 | @param length - if not specifed, entire string is hashed, 123 | otherwise only first length bytes 124 | """ 125 | if self.digest_finalized: 126 | raise DigestError("No updates allowed") 127 | if not isinstance(data, bintype): 128 | raise TypeError("A byte string is expected") 129 | if length is None: 130 | length = len(data) 131 | elif length > len(data): 132 | raise ValueError("Specified length is greater than length of data") 133 | result = libcrypto.EVP_DigestUpdate(self.ctx, c_char_p(data), length) 134 | if result != 1: 135 | raise DigestError("Unable to update digest") 136 | 137 | def digest(self, data=None): 138 | """ 139 | Finalizes digest operation and return digest value 140 | Optionally hashes more data before finalizing 141 | """ 142 | if self.digest_finalized: 143 | return self.digest_out.raw[:self.digest_size] 144 | if data is not None: 145 | self.update(data) 146 | self.digest_out = create_string_buffer(256) 147 | length = c_long(0) 148 | result = libcrypto.EVP_DigestFinal_ex(self.ctx, self.digest_out, 149 | byref(length)) 150 | if result != 1: 151 | raise DigestError("Unable to finalize digest") 152 | self.digest_finalized = True 153 | return self.digest_out.raw[:self.digest_size] 154 | def copy(self): 155 | """ 156 | Creates copy of the digest CTX to allow to compute digest 157 | while being able to hash more data 158 | """ 159 | 160 | new_digest = Digest(self.digest_type) 161 | libcrypto.EVP_MD_CTX_copy(new_digest.ctx, self.ctx) 162 | return new_digest 163 | 164 | def _clean_ctx(self): 165 | """ 166 | Clears and deallocates context 167 | """ 168 | try: 169 | if self.ctx is not None: 170 | libcrypto.EVP_MD_CTX_free(self.ctx) 171 | del self.ctx 172 | except AttributeError: 173 | pass 174 | self.digest_out = None 175 | self.digest_finalized = False 176 | 177 | def hexdigest(self, data=None): 178 | """ 179 | Returns digest in the hexadecimal form. For compatibility 180 | with hashlib 181 | """ 182 | from base64 import b16encode 183 | if pyver == 2: 184 | return b16encode(self.digest(data)) 185 | else: 186 | return b16encode(self.digest(data)).decode('us-ascii') 187 | 188 | 189 | # Declare function result and argument types 190 | libcrypto.EVP_get_digestbyname.restype = c_void_p 191 | libcrypto.EVP_get_digestbyname.argtypes = (c_char_p, ) 192 | # These two functions are renamed in OpenSSL 1.1.0 193 | if hasattr(libcrypto,"EVP_MD_CTX_create"): 194 | Digest.newctx = libcrypto.EVP_MD_CTX_create 195 | Digest.freectx = libcrypto.EVP_MD_CTX_destroy 196 | else: 197 | Digest.newctx = libcrypto.EVP_MD_CTX_new 198 | Digest.freectx = libcrypto.EVP_MD_CTX_free 199 | Digest.newctx.restype = c_void_p 200 | Digest.freectx.argtypes = (c_void_p, ) 201 | # libcrypto.EVP_MD_CTX_create has no arguments 202 | libcrypto.EVP_DigestInit_ex.restype = c_int 203 | libcrypto.EVP_DigestInit_ex.argtypes = (c_void_p, c_void_p, c_void_p) 204 | libcrypto.EVP_DigestUpdate.restype = c_int 205 | libcrypto.EVP_DigestUpdate.argtypes = (c_void_p, c_char_p, c_longlong) 206 | libcrypto.EVP_DigestFinal_ex.restype = c_int 207 | libcrypto.EVP_DigestFinal_ex.argtypes = (c_void_p, c_char_p, POINTER(c_long)) 208 | libcrypto.EVP_MD_CTX_copy.restype = c_int 209 | libcrypto.EVP_MD_CTX_copy.argtypes = (c_void_p, c_void_p) 210 | libcrypto.EVP_MD_type.argtypes = (c_void_p, ) 211 | libcrypto.EVP_MD_size.argtypes = (c_void_p, ) 212 | libcrypto.EVP_MD_block_size.restype = c_int 213 | libcrypto.EVP_MD_block_size.argtypes = (c_void_p, ) 214 | libcrypto.EVP_MD_size.restype = c_int 215 | libcrypto.EVP_MD_size.argtypes = (c_void_p, ) 216 | libcrypto.EVP_MD_type.restype = c_int 217 | libcrypto.EVP_MD_type.argtypes = (c_void_p, ) 218 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ctypescrypto 2 | ============ 3 | 4 | Python interface to some openssl function based on ctypes module 5 | 6 | This module is based on works from 7 | 8 | http://code.google.com/p/ctypescrypto/ 9 | 10 | most recent version can be checked out from 11 | 12 | https://github.com/vbwagner/ctypescrypto.git 13 | 14 | Rationale 15 | --------- 16 | 17 | Why have yet another crypto extension for Python? There is pyopenssl, 18 | m2crypto, hashlib in the standard library and many more. 19 | 20 | But most of these extension implement interfaces to particular set of 21 | cryptoalgorthms. This extension takes an another approach — it uses 22 | algorithm-agnostic EVP layer whenever possible, and so support any 23 | algorithms which are supported by underlying library, even this 24 | algorithms are implemented in the loadable modules (engines). Algorithms 25 | which you've just added to library, should be supported too. 26 | 27 | Also, this extension takes some care of correctly converting textual 28 | information from ASN.1 structures into unicode. 29 | 30 | 31 | Digest calculation 32 | ------------------ 33 | 34 | Module **ctypescrypto.digest** contain **new()** function which produces 35 | objects simular to python **hashlib** module objects. 36 | 37 | On the systems where hashlib is linked with libcrypto dynamically, 38 | hashlib even able to make use of digest types, provided by loadable 39 | engines. 40 | 41 | This module would utilize same copy of libcrypto library as other 42 | ctypescrypto modules, so it would work with engine-provided digests. 43 | 44 | Additionally there is **DigestType** class which may be needed to 45 | construct CMS SignedData objects or add signatures to them. 46 | 47 | 48 | MAC calculation 49 | --------------- 50 | 51 | Mac is Message Authentication Code - it is like keyed digest, which 52 | depends not only on message, but also on key, which should be used both 53 | when initially computing MAC and when verifying it. MACs can be viewed 54 | as "digital signatures with symmetric keys". 55 | 56 | Most common type of MAC is HMAC (i.e. hash-based MAC), described in 57 | [RFC 2104](https://tools.ietf.org/html/rfc2104), but there are other, 58 | for instance [GOST 28147-89](https://tools.ietf.org/html/rfc5830) defines MAC based on symmetric cipher. 59 | Also GSM 0348 uses DES symmetric cipher as MAC. OpenSSL supports 60 | GOST mac via loadable engine module, but doesn't seem to support any 61 | non-HMAC MAC in the core. So, MAC is only test in the test suite which 62 | requires loadable engine. 63 | 64 | Symmetric ciphers 65 | ----------------- 66 | 67 | Module *ctypescrypto.cipher* contain *new()* function which provides 68 | way to create cipher objects. Cipher padding can be configure later. 69 | This object provides methods *update* and *finish* which allows to 70 | encrypt/decrypt data. All ciphers, supported by your version of OpenSSL 71 | and its loadable engines are supported. 72 | 73 | Additionally the **CipherType** class instances may be used directly to 74 | pass to other functions such as CMS EnvelopedData or EncryptedData 75 | **create** 76 | 77 | Public key operations 78 | --------------------- 79 | 80 | Module **ctypescrypto.pkey** provides **PKey** object, which represents 81 | public/private key pair or just public key. With this object you can 82 | sign data, derive shared key and verify signatures. 83 | 84 | This is quite low-level object, which can be used to implement some 85 | non-standard protocols and operations. 86 | 87 | It is possible to extract public key from the certificate as PKey 88 | object (see below). 89 | 90 | Additional module **ctypescrypto.ec** allows to create **PKey** objects 91 | with elliptic curve keys from just raw secret key as byte buffer or 92 | python big integer. 93 | 94 | X509 certificates 95 | ----------------- 96 | 97 | Certificates are cryptographically signed documents, which tie together 98 | public key and some attributes of key owner (certificate subject). 99 | Certificates are signed by some trusted organizations called Certificate 100 | Authorities (one which have issued given certificate, is called 101 | certificate issuer). Your browser or operating system typically have 102 | predefined store of the trusted CA certificates (although nothing 103 | prevent you from running your own CA using openssl command line utility, 104 | and trust only it). 105 | 106 | 107 | 108 | Certificates are described in [RFC 5280](http://tools.ietf.org/html/rfc5280) 109 | 110 | Module **ctypescrypto.x509** contains objects **X509** which represents 111 | certificate (and can be constructed from string, contained PEM 112 | or DER certificate) and object **X509Store** which is a store of trusted 113 | CA certificates which can be used to high-level signature verifications 114 | (i.e. in PKCS7/CMS messages). 115 | 116 | There is no support for creating and signing certificates, i.e. to 117 | perform Certificate Authority functions. This library for now focuses on 118 | cryptography user functionality. 119 | 120 | Certificate has properties corresponding to its subject and issuer 121 | names, public key (of course it is PKey object described above) and 122 | serial number. Subject and issuer names can be indexed by OIDs or by 123 | position of field. Unicode in the names is supported. 124 | 125 | Support for visualising certificate extensions is minimal for now. 126 | Extension object can be converted into string, extension Oid can be 127 | retrieved and critical flag is checked. 128 | 129 | **StackOfX509** implements collection of certificates, necessary for 130 | some operations with CMS and certificate verification. 131 | 132 | CMS documents 133 | ------------- 134 | 135 | CMS stands for Cryptographic Message Syntax. It is defined in the 136 | [RFC 5652](http://tools.ietf.org/html/rfc5652). 137 | CMS defines several types of documents. There is **SignedData**, 138 | which can be read by anyone, but is protected from authorized changes 139 | by digital signature of its author. There is **EnvelopedData** protected 140 | from unauthorized reading by cipher and allowed to be read only by 141 | owners of certain private keys, and there is **EncryptedData**, which 142 | are protected by symmetric cipher keys. 143 | 144 | 145 | There is basic factory function **CMS()**, which parses PEM or der 146 | representation of cryptographic message and generates appropriate 147 | object. There are **SignedData**, **EnvelopedData** and 148 | **EncryptedData** classes. Each class has static method **create** 149 | allowing to create this subtype of message from raw data and appropriate 150 | keys and certificates. 151 | 152 | **SignedData** has **verify()** method. **EnvelopedData** and 153 | **EncryptedData** - **decrypt** method. 154 | 155 | Unfortunately, **SignedAndEnvelopedData** seems to be unsupported in 156 | libcrypto as of version 1.0.1 of OpenSSL. 157 | 158 | PBKDF2 159 | ------ 160 | 161 | Provides interface to password based key derivation function 162 | Interface slightly differs from the **hashlib.pbkdf2_hmac** function, 163 | which have appeared in Python 2.7.8 but functionality is just same, 164 | although OpenSSL implementation might be faster. 165 | 166 | 167 | 168 | OID database 169 | ------------ 170 | 171 | OpenSSL contains internal object identifiers (OID) database. Each OID 172 | have apart from dotted-decimal representation long name, short name and 173 | numeric identifier. Module **ctypescrypto.oid** provides interface to the 174 | database. **Oid** objects store numeric identifier internally and can 175 | return both long and short name and dotted-decimal representation. 176 | 177 | BIO library 178 | ----------- 179 | 180 | OpenSSL contain BIO (basic input-output) abstraction. And all object 181 | serialization/deserialization use this library. Also human-readable 182 | representation of ASN.1 structures use this library extensively. So, 183 | we've to develop python object which allow to read from python string 184 | via BIO abstraction or write to buffer, which can be returned as python 185 | string or unicode object. 186 | 187 | Exceptions 188 | ---------- 189 | 190 | Exceptions, used in the **ctypescrypto** to report problems are tied 191 | closely with OpenSSL error-reporting functions, so if such an exception 192 | occurs, as much as possibly information from inside libcrypto would be 193 | available in the Python 194 | 195 | Engine support 196 | -------------- 197 | 198 | There is just one function **ctypescrypt.engine.set_default**, which loads 199 | specified engine by id and makes it default for all algorithms, 200 | supported by it. It is enough for me to use Russian national 201 | cryptographic algorithms, provided by **gost** engine. 202 | 203 | Test Suite 204 | ---------- 205 | 206 | Tests can be run using 207 | 208 | python setup.py test 209 | 210 | Test suite is fairly incomplete. Contributions are welcome. 211 | 212 | Note that you need properly installed OpenSSL library with set of CA 213 | certificates in the certs directory, otherwise default certstore test 214 | would fail. 215 | 216 | You also need gost engine to be available (check with 217 | 218 | openssl engine gost 219 | 220 | ) otherwise mac test would crash with error. Unfortunately there is no 221 | non-HMAC MAC in the openssl core, so GOST MAC is only option. 222 | 223 | OpenSSL 1.0 includes GOST engine by default. For OpenSSL 1.1 and above 224 | GOST engine is developed as separate project and can be downloaded from 225 | [https://github.com/gost-engine/engine](https://github.com/gost-engine/engine) 226 | Debian buster and above includes gost engine as 227 | libengine-gost-openssl1.1 package. 228 | 229 | 230 | Possible future enhancements 231 | ---------------------------- 232 | 233 | 1. Creation and signing of the certificate requests (PKCS#10) 234 | 2. Parsing and analyzing CRLs 235 | 3. OCSP ([RFC 6960](http://tools.ietf.org/html/rfc6960))request creation and response parsing 236 | 4. Timestamping ([RFC 3161](http://tools.ietf.org/html/rfc3161)) 237 | support. 238 | 239 | vim: spelllang=en tw=72 240 | -------------------------------------------------------------------------------- /tests/testpkey.py: -------------------------------------------------------------------------------- 1 | from ctypescrypto.pkey import PKey 2 | from ctypescrypto import pyver 3 | import unittest,re 4 | from base64 import b64decode, b16decode 5 | from subprocess import Popen,PIPE,CalledProcessError 6 | 7 | def pem2der(s): 8 | start=s.find('-----\n') 9 | finish=s.rfind('\n-----END') 10 | data=s[start+6:finish] 11 | return b64decode(data) 12 | 13 | def runopenssl(args,indata): 14 | p=Popen(['openssl']+args,stdin=PIPE,stdout=PIPE,stderr=PIPE) 15 | (out,err)=p.communicate(indata) 16 | if p.returncode: 17 | raise CalledProcessError(p.returncode," ".join(['openssl']+args)+":"+err) 18 | if pyver > 2: 19 | out = out.decode("utf-8") 20 | return out 21 | 22 | 23 | class TestPKey(unittest.TestCase): 24 | rsa="""-----BEGIN PRIVATE KEY----- 25 | MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL9CzVZu9bczTmB8 26 | 776pPUoPo6WbAfwQqqiGrj91bk2mYE+MNLo4yIQH45IcwGzkyS8+YyQJf8Bux5BC 27 | oZ2nwzXm5+JZkxkN1mtMzit2D7/hHmrZLoSbr0sxXFrD4a35RI4hXnSK9Sk01sXA 28 | Te2OgHzm5nk1sG97G6SFq7CHe3gvAgMBAAECgYAgGV8K7Y5xk7vIt88oyZCOuHc3 29 | mP9JRabOp+PgpJ3BjHXHg/lpc5Q7jHNmF0s4O2GEe0z6RFnbevwlOvmS0xAQ1hpg 30 | 5TnVVkiZvcJeQaZqWIlEOaLqA12YdhSyorfB6p3tfQ7ZmQusg3SCsru5kPJV4sm0 31 | I+MuRCQZWSzIqelloQJBAPbtScJI0lXx8sktntZi69cAVvLtz5z1T7bZwFakNkNE 32 | SUCjNc/hEEI6/1LScV8Kx9kgQ0+W8smu+GyUDceyVFECQQDGSeS7cTmojkiPQxPB 33 | zb0vS+Nfpq6oYsx+gn5TBgMeWIZZrtMEaUU2o+rwsjKBP/gy6D1zC2b4W5O/A/7a 34 | 1GR/AkBUQhYoKKc1UpExGtMXfrvRKrmAvatZeM/Rqi4aooAtpfCFEOw82iStJOqY 35 | /VxYPRqCuaKeVvjT31O/4SlumihxAkBahRU0NKYbuoiJThfQ23lIBB7SZadKG4A7 36 | KJs+j3oQ+lyqyFJwqxX7sazpIJBJzMgjhT24LTZenn++LbbEcz1FAkBmDmxoq7qO 37 | Ao6uTm8fnkD4C836wS4mYAPqwRBK1JvnEXEQee9irf+ip89BAg74ViTcGF9lwJwQ 38 | gOM+X5Db+3pK 39 | -----END PRIVATE KEY----- 40 | """ 41 | rsaenc="""-----BEGIN RSA PRIVATE KEY----- 42 | Proc-Type: 4,ENCRYPTED 43 | DEK-Info: AES-256-CBC,7FF0E46291D60D35ACA881131C244655 44 | 45 | BeJoui1lRQDvvPr+gH8xCdqkcgKCLWpZTvFZmvrqXmPqMHpm20nK0ESAd6kKm8d1 46 | zaglRIHnnO6V7aDcwgOd3IYPEOnG2TIRQniZrwZdrXIfacscJ6Ekq+5YfLuyrRgq 47 | fscGl7ntm/eGLqwrhzuv7jAXpn9QWiiAld0EWcmZCAW7nGaUQtu4rc4ULwL5SC/M 48 | MOCPwpcD3SCQv55dX3cBOtfZ3lPbpgEpTpnNnj8OtxOkkIaG8yol7luwHvoOSyL/ 49 | WuXGCpfJE4LzbxnSLhbiN7q+y/Sro3cGc9aO4tXToMqTFel4zqR0YgOeFazlDRi1 50 | mPuZcGLuSIef0kJn7Mg7jt0DQk579rTVxAhIu2rylTwEozkpCp5g4kGTJON++HQr 51 | BRrApm4XlAoH2GX1jqDBoWSnXCRH49jNGQrLwy469i+994cG8uVU9Z5cqm/LDIR9 52 | kwQfTJIvMi0g28NBMVgJ2gLj40OczxDGyNvBIbhPNswHljfsvPVr4vtxDGx8fS0N 53 | lUJUOL9me+XNZ5xGHYuT5DOr7GE+H3hKEg+XfrYEete9BeI4gm9cqESvrLY9EU5Q 54 | tOtnKKL7SglTZ5LxPMAedADC0o01nzr+D3gAiOhSgoZTrnQsSZ7iTJOtm3vNXwJx 55 | AgesYmXtr5mdiBPKQ1QA/jF5LUZji+5KENd5WHNQw7tOlMLDrPFVRfLZg1AQDljx 56 | u16kdyb71Kk3f6GCOfUntGr+kzppc3DDT+RcLetXphOOEQRy6C6/wmz08WlAPlu5 57 | mFfSDijpWxoUHooQISg5mE82oR8V81aBpbLtm7KevwY= 58 | -----END RSA PRIVATE KEY----- 59 | """ 60 | pkcs8crypt="""-----BEGIN ENCRYPTED PRIVATE KEY----- 61 | MIICoTAbBgkqhkiG9w0BBQMwDgQIipVEnsV/gQoCAggABIICgE1i42C4aBhykhOi 62 | EItFRE+9iBgiklGxoCJtukdp1UwDRKy/GJJ1rcS385CQy4Rs0zN8NH1faVRbf4Vt 63 | iNACHtJx30qMCdo64CR+GJYHS4g2lGaz7PFNma8SjnAbGYXwXkdm5zhwmiU++wC7 64 | W59u8oWS8Dj9dZBMzoOQGQT6xzZwQ14H65zHvC16HdKSNtRgXDWkBnD2cQzuOyuf 65 | rFLyEf7/FH6B7/yKDcwsEfu97uPPxMvuusD1UubWnltO/Hc2oCPibN+dGw1PY9mC 66 | 18yGQtZkf5z30yhLosF62IVy3XY9Yf/TJYojIExoASrThGRvENzWkQ3qfnErqmng 67 | l+dy66bmLjicobF5bO3xAhpU1dL+4/1ba2QuthVNlg6Or/iII1ntNN4PFyYcEwmX 68 | e09C3dyOtV7qCq13S1bRkbZhzwi2QbLKALAzrZpF6VYmayTz8KjQOZ8BncAM+BiI 69 | CtwuZJoXLW9kT4D7UsaSZdjUvzBIak5qdCGWpKmahMfjEEsCg6ApuIYmFrCgiY9c 70 | 0keYjY8DJ+4bEvqsQvTIaU9F9mFytI1E3LnR0NP1jHuOA7Jc+oNQ2adgFNj12jKQ 71 | qNt1bEGNCqQHSrw7JNCrB7s+QAFNqJtno6fIq7vVNkqadJlnBbCIgm7NlJeGg9j6 72 | a5YVNGlbs0J4dQF4Jw13302IBn3piSzthWL2gL98v/1lEwGuernEpPAjry3YhzM9 73 | VA/oVt22n3yVA6dOSVL1oUTJyawEqASmH0jHAzXNDz+QLSLmz82ARcZPqPvVc45e 74 | 5h0xtqtFVkQLNbYzpNWGrx7R1hdr84nOKa8EsIxTRgEL/w9Y4Z/3xEoK2+KVBpMk 75 | oxUuxuU= 76 | -----END ENCRYPTED PRIVATE KEY----- 77 | """ 78 | password="1111" 79 | rsakeytext="""Public-Key: (1024 bit) 80 | Modulus: 81 | 00:bf:42:cd:56:6e:f5:b7:33:4e:60:7c:ef:be:a9: 82 | 3d:4a:0f:a3:a5:9b:01:fc:10:aa:a8:86:ae:3f:75: 83 | 6e:4d:a6:60:4f:8c:34:ba:38:c8:84:07:e3:92:1c: 84 | c0:6c:e4:c9:2f:3e:63:24:09:7f:c0:6e:c7:90:42: 85 | a1:9d:a7:c3:35:e6:e7:e2:59:93:19:0d:d6:6b:4c: 86 | ce:2b:76:0f:bf:e1:1e:6a:d9:2e:84:9b:af:4b:31: 87 | 5c:5a:c3:e1:ad:f9:44:8e:21:5e:74:8a:f5:29:34: 88 | d6:c5:c0:4d:ed:8e:80:7c:e6:e6:79:35:b0:6f:7b: 89 | 1b:a4:85:ab:b0:87:7b:78:2f 90 | Exponent: 65537 (0x10001) 91 | """ 92 | ec1priv="""-----BEGIN PRIVATE KEY----- 93 | MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgKnG6neqZvB98EEuuxnHs 94 | fv+L/5abuNNG20wzUqRpncOhRANCAARWKXWeUZ6WiCKZ2kHx87jmJyx0G3ZB1iQC 95 | +Gp2AJYswbQPhGPigKolzIbZYfwnn7QOca6N8QDhPAn3QQK8trZI 96 | -----END PRIVATE KEY----- 97 | """ 98 | ec1keytext="""Public-Key: (256 bit) 99 | pub: 100 | 04:56:29:75:9e:51:9e:96:88:22:99:da:41:f1:f3: 101 | b8:e6:27:2c:74:1b:76:41:d6:24:02:f8:6a:76:00: 102 | 96:2c:c1:b4:0f:84:63:e2:80:aa:25:cc:86:d9:61: 103 | fc:27:9f:b4:0e:71:ae:8d:f1:00:e1:3c:09:f7:41: 104 | 02:bc:b6:b6:48 105 | ASN1 OID: secp256k1 106 | """ 107 | ec1pub="""-----BEGIN PUBLIC KEY----- 108 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEVil1nlGelogimdpB8fO45icsdBt2QdYk 109 | AvhqdgCWLMG0D4Rj4oCqJcyG2WH8J5+0DnGujfEA4TwJ90ECvLa2SA== 110 | -----END PUBLIC KEY----- 111 | """ 112 | 113 | def test_unencrypted_pem(self): 114 | key=PKey(privkey=self.rsa) 115 | self.assertTrue(key.cansign) 116 | self.assertIsNotNone(key.key) 117 | self.assertEqual(str(key),self.rsakeytext) 118 | def test_encrypted_pem(self): 119 | key=PKey(privkey=self.rsaenc,password=self.password) 120 | self.assertIsNotNone(key.key) 121 | self.assertEqual(str(key),self.rsakeytext) 122 | def test_encrypted_pem_cb(self): 123 | cb=lambda x:self.password 124 | key=PKey(privkey=self.rsaenc,password=cb) 125 | self.assertIsNotNone(key.key) 126 | self.assertEqual(str(key),self.rsakeytext) 127 | def test_encryped_pem_pkcs8(self): 128 | key=PKey(privkey=self.pkcs8crypt,password=self.password) 129 | self.assertIsNotNone(key.key) 130 | self.assertEqual(str(key),self.rsakeytext) 131 | def test_encrypted_der_pkcs8(self): 132 | pkcs8der = pem2der(self.pkcs8crypt) 133 | key=PKey(privkey=pkcs8der,password=self.password,format="DER") 134 | self.assertIsNotNone(key.key) 135 | self.assertEqual(str(key),self.rsakeytext) 136 | def test_export_priv_pem(self): 137 | key=PKey(privkey=self.ec1priv) 138 | out=key.exportpriv() 139 | self.assertEqual(self.ec1priv,out) 140 | def test_export_priv_encrypt(self): 141 | from ctypescrypto.cipher import CipherType 142 | key=PKey(privkey=self.rsa) 143 | pem=key.exportpriv(password='2222',cipher=CipherType("aes256")) 144 | if pyver >2: 145 | pem = pem.encode("ascii") 146 | self.assertEqual(runopenssl(["pkey","-text_pub","-noout","-passin","pass:2222"], 147 | pem),self.rsakeytext) 148 | def test_export_priv_der(self): 149 | key=PKey(privkey=self.rsa) 150 | der=key.exportpriv(format="DER") 151 | self.assertEqual(runopenssl(["pkey","-text_pub","-noout","-inform","DER"], 152 | der),self.rsakeytext) 153 | def test_export_priv_der_enc(self): 154 | from ctypescrypto.cipher import CipherType 155 | key=PKey(privkey=self.rsa) 156 | der=key.exportpriv(format="DER",password='2222',cipher=CipherType("aes256")) 157 | self.assertEqual(runopenssl(["pkcs8","-passin","pass:2222","-inform","DER"], 158 | der),self.rsa) 159 | def test_unencrypted_pem_ec(self): 160 | 161 | key=PKey(privkey=self.ec1priv) 162 | self.assertIsNotNone(key.key) 163 | self.assertEqual(re.sub("pub: \n","pub:\n",str(key)),self.ec1keytext) 164 | def test_unencrypted_der_ec(self): 165 | key=PKey(privkey=pem2der(self.ec1priv),format="DER") 166 | self.assertIsNotNone(key.key) 167 | self.assertEqual(re.sub("pub: \n","pub:\n",str(key)),self.ec1keytext) 168 | def test_pubkey_pem(self): 169 | key=PKey(pubkey=self.ec1pub) 170 | self.assertIsNotNone(key.key) 171 | self.assertEqual(re.sub("pub: \n","pub:\n",str(key)),self.ec1keytext) 172 | def test_pubkey_der(self): 173 | key=PKey(pubkey=pem2der(self.ec1pub),format="DER") 174 | self.assertIsNotNone(key.key) 175 | self.assertEqual(re.sub("pub: \n","pub:\n",str(key)),self.ec1keytext) 176 | def test_compare(self): 177 | key1=PKey(privkey=self.ec1priv) 178 | self.assertIsNotNone(key1.key) 179 | key2=PKey(pubkey=self.ec1pub) 180 | self.assertIsNotNone(key2.key) 181 | self.assertEqual(key1,key2) 182 | def test_sign(self): 183 | signer=PKey(privkey=self.ec1priv) 184 | digest=b16decode("FFCA2587CFD4846E4CB975B503C9EB940F94566AA394E8BD571458B9DA5097D5") 185 | signature=signer.sign(digest) 186 | self.assertTrue(len(signature)>0) 187 | verifier=PKey(pubkey=self.ec1pub) 188 | self.assertTrue(verifier.verify(digest,signature)) 189 | def test_generate(self): 190 | newkey=PKey.generate("rsa") 191 | self.assertIsNotNone(newkey.key) 192 | s=str(newkey) 193 | self.assertEqual(s[:s.find("\n")],"Public-Key: (1024 bit)") 194 | def test_generate_params(self): 195 | newkey=PKey.generate("rsa",rsa_keygen_bits=2048) 196 | self.assertIsNotNone(newkey.key) 197 | s=str(newkey) 198 | self.assertEqual(s[:s.find("\n")],"Public-Key: (2048 bit)") 199 | def test_generate_ec(self): 200 | templkey=PKey(pubkey=self.ec1pub) 201 | newkey=PKey.generate("ec",paramsfrom=templkey) 202 | self.assertIsNotNone(newkey.key) 203 | s=str(newkey) 204 | self.assertEqual(s[:s.find("\n")],"Public-Key: (256 bit)") 205 | self.assertNotEqual(str(templkey),str(newkey)) 206 | if __name__ == "__main__": 207 | unittest.main() 208 | -------------------------------------------------------------------------------- /ctypescrypto/cipher.py: -------------------------------------------------------------------------------- 1 | """ 2 | access to symmetric ciphers from libcrypto 3 | 4 | """ 5 | from ctypes import create_string_buffer, c_char_p, c_void_p, c_int 6 | from ctypes import byref, POINTER 7 | from ctypescrypto import libcrypto, pyver, bintype 8 | from ctypescrypto.exception import LibCryptoError 9 | from ctypescrypto.oid import Oid 10 | 11 | CIPHER_ALGORITHMS = ("DES", "DES-EDE3", "BF", "AES-128", "AES-192", "AES-256") 12 | CIPHER_MODES = ("STREAM", "ECB", "CBC", "CFB", "OFB", "CTR", "GCM") 13 | 14 | # 15 | 16 | __all__ = ['CipherError', 'new', 'Cipher', 'CipherType'] 17 | 18 | class CipherError(LibCryptoError): 19 | """ 20 | Exception raise when OpenSSL function returns error 21 | """ 22 | pass 23 | 24 | def new(algname, key, encrypt=True, iv=None): 25 | """ 26 | Returns new cipher object ready to encrypt-decrypt data 27 | 28 | @param algname - string algorithm name like in opemssl command 29 | line 30 | @param key - binary string representing ciher key 31 | @param encrypt - if True (default) cipher would be initialized 32 | for encryption, otherwise - for decrypton 33 | @param iv - initialization vector 34 | """ 35 | ciph_type = CipherType(algname) 36 | return Cipher(ciph_type, key, iv, encrypt) 37 | 38 | class CipherType(object): 39 | """ 40 | Describes cihper algorihm. Can be used to produce cipher 41 | instance and to get various information about cihper 42 | """ 43 | 44 | def __init__(self, cipher_name): 45 | """ 46 | Constructs cipher algortihm using textual name as in openssl 47 | command line 48 | """ 49 | if pyver > 2: 50 | cipher_name = cipher_name.encode('utf-8') 51 | self.cipher = libcrypto.EVP_get_cipherbyname(cipher_name) 52 | if self.cipher is None: 53 | raise CipherError("Unknown cipher: %s" % cipher_name) 54 | 55 | def __del__(self): 56 | """ 57 | It is constant object with do-nothing del 58 | """ 59 | pass 60 | 61 | def block_size(self): 62 | """ 63 | Returns block size of the cipher 64 | """ 65 | return libcrypto.EVP_CIPHER_block_size(self.cipher) 66 | 67 | def key_length(self): 68 | """ 69 | Returns key length of the cipher 70 | """ 71 | return libcrypto.EVP_CIPHER_key_length(self.cipher) 72 | 73 | def iv_length(self): 74 | """ 75 | Returns initialization vector length of the cipher 76 | """ 77 | return libcrypto.EVP_CIPHER_iv_length(self.cipher) 78 | 79 | def flags(self): 80 | """ 81 | Return cipher flags. Low three bits of the flags encode 82 | cipher mode (see mode). Higher bits is combinatuon of 83 | EVP_CIPH* constants defined in the 84 | """ 85 | return libcrypto.EVP_CIPHER_flags(self.cipher) 86 | 87 | def mode(self): 88 | """ 89 | Returns cipher mode as string constant like CBC, OFB etc. 90 | """ 91 | return CIPHER_MODES[self.flags() & 0x7] 92 | 93 | def algo(self): 94 | """ 95 | Return cipher's algorithm name, derived from OID 96 | """ 97 | return self.oid().shortname() 98 | 99 | def oid(self): 100 | """ 101 | Returns ASN.1 object identifier of the cipher as 102 | ctypescrypto.oid.Oid object 103 | """ 104 | return Oid(libcrypto.EVP_CIPHER_nid(self.cipher)) 105 | 106 | class Cipher(object): 107 | """ 108 | Performs actual encrypton decryption 109 | Note that object keeps some internal state. 110 | To obtain full ciphertext (or plaintext during decihpering) 111 | user should concatenate results of all calls of update with 112 | result of finish 113 | """ 114 | def __init__(self, cipher_type, key, iv, encrypt=True): 115 | """ 116 | Initializing cipher instance. 117 | 118 | @param cipher_type - CipherType object 119 | @param key = binary string representing the key 120 | @param iv - binary string representing initializtion vector 121 | @param encrypt - if True(default) we ere encrypting. 122 | Otherwise decrypting 123 | 124 | """ 125 | self._clean_ctx() 126 | # Check key and iv length 127 | if key is None: 128 | raise ValueError("No key specified") 129 | 130 | key_ptr = c_char_p(key) 131 | iv_ptr = c_char_p(iv) 132 | self.ctx = libcrypto.EVP_CIPHER_CTX_new() 133 | if self.ctx == 0: 134 | raise CipherError("Unable to create cipher context") 135 | self.encrypt = encrypt 136 | enc = 1 if encrypt else 0 137 | if not iv is None and len(iv) != cipher_type.iv_length(): 138 | raise ValueError("Invalid IV length for this algorithm") 139 | 140 | if len(key) != cipher_type.key_length(): 141 | if (cipher_type.flags() & 8) != 0: 142 | # Variable key length cipher. 143 | result = libcrypto.EVP_CipherInit_ex(self.ctx, 144 | cipher_type.cipher, 145 | None, None, None, 146 | c_int(enc)) 147 | result = libcrypto.EVP_CIPHER_CTX_set_key_length(self.ctx, 148 | len(key)) 149 | if result == 0: 150 | self._clean_ctx() 151 | raise CipherError("Unable to set key length") 152 | result = libcrypto.EVP_CipherInit_ex(self.ctx, None, None, 153 | key_ptr, iv_ptr, 154 | c_int(enc)) 155 | else: 156 | raise ValueError("Invalid key length for this algorithm") 157 | else: 158 | result = libcrypto.EVP_CipherInit_ex(self.ctx, cipher_type.cipher, 159 | None, key_ptr, iv_ptr, 160 | c_int(enc)) 161 | if result == 0: 162 | self._clean_ctx() 163 | raise CipherError("Unable to initialize cipher") 164 | self.cipher_type = cipher_type 165 | self.block_size = self.cipher_type.block_size() 166 | self.cipher_finalized = False 167 | 168 | def __del__(self): 169 | """ 170 | We define _clean_ctx() to do all the cleanup 171 | """ 172 | self._clean_ctx() 173 | 174 | def padding(self, padding=True): 175 | """ 176 | Sets padding mode of the cipher 177 | """ 178 | padding_flag = 1 if padding else 0 179 | libcrypto.EVP_CIPHER_CTX_set_padding(self.ctx, padding_flag) 180 | 181 | def update(self, data): 182 | """ 183 | Performs actual encrypton/decrypion 184 | 185 | @param data - part of the plain text/ciphertext to process 186 | @returns - part of ciphercext/plain text 187 | 188 | Passed chunk of text doesn't need to contain full ciher 189 | blocks. If neccessery, part of passed data would be kept 190 | internally until next data would be received or finish 191 | called 192 | """ 193 | if self.cipher_finalized: 194 | raise CipherError("No updates allowed") 195 | if not isinstance(data, bintype): 196 | raise TypeError("A byte string is expected") 197 | if len(data) == 0: 198 | return "" 199 | outbuf = create_string_buffer(self.block_size+len(data)) 200 | outlen = c_int(0) 201 | ret = libcrypto.EVP_CipherUpdate(self.ctx, outbuf, byref(outlen), 202 | data, len(data)) 203 | if ret <= 0: 204 | self._clean_ctx() 205 | self.cipher_finalized = True 206 | raise CipherError("problem processing data") 207 | return outbuf.raw[:int(outlen.value)] 208 | 209 | def finish(self): 210 | """ 211 | Finalizes processing. If some data are kept in the internal 212 | state, they would be processed and returned. 213 | """ 214 | if self.cipher_finalized: 215 | raise CipherError("Cipher operation is already completed") 216 | outbuf = create_string_buffer(self.block_size) 217 | self.cipher_finalized = True 218 | outlen = c_int(0) 219 | result = libcrypto.EVP_CipherFinal_ex(self.ctx, outbuf, byref(outlen)) 220 | if result == 0: 221 | self._clean_ctx() 222 | raise CipherError("Unable to finalize cipher") 223 | if outlen.value > 0: 224 | return outbuf.raw[:int(outlen.value)] 225 | else: 226 | return b"" 227 | 228 | def _clean_ctx(self): 229 | """ 230 | Cleans up cipher ctx and deallocates it 231 | """ 232 | try: 233 | if self.ctx is not None: 234 | self.__ctxcleanup(self.ctx) 235 | libcrypto.EVP_CIPHER_CTX_free(self.ctx) 236 | del self.ctx 237 | except AttributeError: 238 | pass 239 | self.cipher_finalized = True 240 | 241 | 242 | # 243 | # Used C function block_size 244 | # 245 | libcrypto.EVP_CIPHER_block_size.argtypes = (c_void_p, ) 246 | 247 | #Function EVP_CIPHER_CTX_cleanup renamed to EVP_CIPHER_CTX_reset 248 | # in the OpenSSL 1.1.0 249 | if hasattr(libcrypto,"EVP_CIPHER_CTX_cleanup"): 250 | Cipher.__ctxcleanup = libcrypto.EVP_CIPHER_CTX_cleanup 251 | else: 252 | Cipher.__ctxcleanup = libcrypto.EVP_CIPHER_CTX_reset 253 | Cipher.__ctxcleanup.argtypes = (c_void_p, ) 254 | libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p, ) 255 | libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p 256 | libcrypto.EVP_CIPHER_CTX_set_padding.argtypes = (c_void_p, c_int) 257 | libcrypto.EVP_CipherFinal_ex.argtypes = (c_void_p, c_char_p, POINTER(c_int)) 258 | libcrypto.EVP_CIPHER_flags.argtypes = (c_void_p, ) 259 | libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_void_p, c_char_p, 260 | c_char_p, c_int) 261 | libcrypto.EVP_CIPHER_iv_length.argtypes = (c_void_p, ) 262 | libcrypto.EVP_CIPHER_key_length.argtypes = (c_void_p, ) 263 | libcrypto.EVP_CIPHER_nid.argtypes = (c_void_p, ) 264 | libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_char_p, POINTER(c_int), 265 | c_char_p, c_int) 266 | libcrypto.EVP_get_cipherbyname.restype = c_void_p 267 | libcrypto.EVP_get_cipherbyname.argtypes = (c_char_p, ) 268 | libcrypto.EVP_CIPHER_CTX_set_key_length.argtypes = (c_void_p, c_int) 269 | -------------------------------------------------------------------------------- /tests/testx509.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | 4 | from ctypescrypto import chartype, bintype, inttype 5 | from ctypescrypto.x509 import X509,X509Store,utc,StackOfX509 6 | from ctypescrypto.oid import Oid 7 | from tempfile import NamedTemporaryFile 8 | import datetime 9 | import unittest 10 | import os 11 | 12 | 13 | 14 | class TestCertInfo(unittest.TestCase): 15 | ca_cert="""-----BEGIN CERTIFICATE----- 16 | MIIDxzCCAq+gAwIBAgIJAJ3NNJJNENcOMA0GCSqGSIb3DQEBCwUAMHoxMDAuBgNV 17 | BAoMJ9Cj0LTQvtGB0YLQvtCy0LXRgNGP0Y7RidC40Lkg0YbQtdC90YLRgDEiMCAG 18 | A1UEAwwZ0JLQuNC60YLQvtGAINCS0LDQs9C90LXRgDELMAkGA1UEBhMCUlUxFTAT 19 | BgNVBAgMDNCc0L7RgdC60LLQsDAeFw0xODA0MjkxMjA3NTBaFw0xODA1MjkxMjA3 20 | NTBaMHoxMDAuBgNVBAoMJ9Cj0LTQvtGB0YLQvtCy0LXRgNGP0Y7RidC40Lkg0YbQ 21 | tdC90YLRgDEiMCAGA1UEAwwZ0JLQuNC60YLQvtGAINCS0LDQs9C90LXRgDELMAkG 22 | A1UEBhMCUlUxFTATBgNVBAgMDNCc0L7RgdC60LLQsDCCASIwDQYJKoZIhvcNAQEB 23 | BQADggEPADCCAQoCggEBALUXej45EASSw0LRzBQdrktr17KrCaPuVePvdMZYLv6R 24 | bN14WwLAAMhk2YfKRSsiv6Vv+n6waWXLKxSKcbS+GU0aHJ/0o85ay7lbZCxvk1Fq 25 | pIZeEeILLYOkS+HhFzVXZ4OarO/eZl+tbB1aR/locA5xCHiMqo6pwWRoHMHNriWw 26 | DAH3bUxe3DBi3wjQRBq6V6SolDUjjX1CHDH6zaCoe2cQAPsqti6vOG2q08MzEYuC 27 | 6yDQhjNgXaziUtMo/wpmOsHReTGd44TyMG0FSu8Rs4kVueVBIHjKQpHYMpcb4PI/ 28 | lhbQgI6495KRU7d5o0FoEsdmcr7ZJ+tFTIUkudBeSA8CAwEAAaNQME4wHQYDVR0O 29 | BBYEFCqBD8LZD/Gr4TMy1YyzqzCFt/BEMB8GA1UdIwQYMBaAFCqBD8LZD/Gr4TMy 30 | 1YyzqzCFt/BEMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ3tJjXO 31 | c3zUvVQLmUN9J1UcDx9HTNvarOrUXqOiO8ZZIsWdk9+wWXq6oSktdM/ksLL2XyOB 32 | rDbFFgVZnniTJufutWUsyQxpSe5eEhqBCqeAPVKEj1AulPgMs8zM6s/uOnTw1W35 33 | RzCewrHAnrbQo/hSmzYR101C6k8lUr3jyr69LxIzcqURKdEoatxHH2pFkY1uSkIi 34 | qzJrUJeJQrHq1yub2cuNvB/WDmjwdLtPHADtwWRaU65/9yVr5/f54aztuodGM4iX 35 | hfB1qdrCFhaN3ku6/VUIpMdnuxcYTx7b2M4KDwG6t5wNCOdfHoMzYYJ6wJbk8pdH 36 | 9oCVJRRUrpqSB8k= 37 | -----END CERTIFICATE----- 38 | """ 39 | cert1="""-----BEGIN CERTIFICATE----- 40 | MIIEVDCCAzygAwIBAgIJAPlBrd9jYtl5MA0GCSqGSIb3DQEBCwUAMHoxMDAuBgNV 41 | BAoMJ9Cj0LTQvtGB0YLQvtCy0LXRgNGP0Y7RidC40Lkg0YbQtdC90YLRgDEiMCAG 42 | A1UEAwwZ0JLQuNC60YLQvtGAINCS0LDQs9C90LXRgDELMAkGA1UEBhMCUlUxFTAT 43 | BgNVBAgMDNCc0L7RgdC60LLQsDAeFw0xODA0MjkxMjM4MDZaFw0yODA0MjYxMjM4 44 | MDZaMIGBMQswCQYDVQQGEwJSVTEVMBMGA1UECAwM0JzQvtGB0LrQstCwMRUwEwYD 45 | VQQHDAzQnNC+0YHQutCy0LAxIDAeBgNVBAoMF9Cn0LDRgdGC0L3QvtC1INC70LjR 46 | htC+MSIwIAYDVQQDDBnQktC40LrRgtC+0YAg0JLQsNCz0L3QtdGAMIIBIjANBgkq 47 | hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvwAU7NxWPzy1zmrQRACzWiIRB15bRRg 48 | bMoBjdalivIUPrmOBHcWGl16tFZHikDBoTNUPcExyzqeLaAARGG4SIc/mv3tdQyA 49 | 5Mt02UciW6GIMDMvpz97EY1Zr7fTzYKDPAeF0vaYM5e98Rf0J5OB9NVMxaeylc81 50 | u4WBCLy4H3lwixB2cPbs0UCC/Pt/hXKlWukkFjGFm0xspu62IRDvuyEkJXhcryrB 51 | VN1WEXNCRwfFEqO+Og4/L+hh9ZyNchbfIJdjenytA1HS9jHpB/C0x32eHCunC2Uf 52 | 7/rSWHtRAaKf67wNcM3TO25cnoWYfitZ8dj0LgMVCpZQMbwk8qYWgwIDAQABo4HU 53 | MIHRMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBMGA1UdJQQMMAoGCCsGAQUFBwMB 54 | MB0GA1UdDgQWBBSvfzRZ/IT2OHoNscl5MuifIqOstzAfBgNVHSMEGDAWgBQqgQ/C 55 | 2Q/xq+EzMtWMs6swhbfwRDBDBggrBgEFBQcBAQQ3MDUwMwYIKwYBBQUHMAKGJ2h0 56 | dHA6Ly93d3cud2FnbmVyLnBwLnJ1LzQ1LmZyZWUubmV0LmRlcjAdBgNVHREEFjAU 57 | gRJ2aXR1c0B3YWduZXIucHAucnUwDQYJKoZIhvcNAQELBQADggEBAFq3Xle9+pwE 58 | zBhXXCYJZh4b0KGXYp/Ru7kOqNXKWz6rIcxfqozrKZo8qSkxK/g2KOSXM4jXCy1p 59 | vfl3qproieewQmHTNvuVSnOjGNSkpsj2fkx7ew5mKb/rrH97a8JU1BhESOR2D32h 60 | 0d6l3eROs+nSPxQ8i1FIudbVMp6kg2cY7WEZRgijGlOar3M4SdYUYT5GJvO7cPm/ 61 | B9JpzGu20rEegXftdjyjUj4k0PuIw/fYN9lHrYh/+TK8IMHAZ4nhsqwZH1IWjTsk 62 | iJW/uZYOIYhHMieEEoVcgAEKL5uiYB95O690Qtm6Q0hKbIzCClQSuaCSKWQRVBfz 63 | mBDJd6+znkw= 64 | -----END CERTIFICATE----- 65 | """ 66 | pubkey1="""-----BEGIN PUBLIC KEY----- 67 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvwAU7NxWPzy1zmrQRAC 68 | zWiIRB15bRRgbMoBjdalivIUPrmOBHcWGl16tFZHikDBoTNUPcExyzqeLaAARGG4 69 | SIc/mv3tdQyA5Mt02UciW6GIMDMvpz97EY1Zr7fTzYKDPAeF0vaYM5e98Rf0J5OB 70 | 9NVMxaeylc81u4WBCLy4H3lwixB2cPbs0UCC/Pt/hXKlWukkFjGFm0xspu62IRDv 71 | uyEkJXhcryrBVN1WEXNCRwfFEqO+Og4/L+hh9ZyNchbfIJdjenytA1HS9jHpB/C0 72 | x32eHCunC2Uf7/rSWHtRAaKf67wNcM3TO25cnoWYfitZ8dj0LgMVCpZQMbwk8qYW 73 | gwIDAQAB 74 | -----END PUBLIC KEY----- 75 | """ 76 | digicert_cert="""digicert.crt 77 | -----BEGIN CERTIFICATE----- 78 | MIIG5jCCBc6gAwIBAgIQAze5KDR8YKauxa2xIX84YDANBgkqhkiG9w0BAQUFADBs 79 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 80 | d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j 81 | ZSBFViBSb290IENBMB4XDTA3MTEwOTEyMDAwMFoXDTIxMTExMDAwMDAwMFowaTEL 82 | MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 83 | LmRpZ2ljZXJ0LmNvbTEoMCYGA1UEAxMfRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug 84 | RVYgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPOWYth1bhn/ 85 | PzR8SU8xfg0ETpmB4rOFVZEwscCvcLssqOcYqj9495BoUoYBiJfiOwZlkKq9ZXbC 86 | 7L4QWzd4g2B1Rca9dKq2n6Q6AVAXxDlpufFP74LByvNK28yeUE9NQKM6kOeGZrzw 87 | PnYoTNF1gJ5qNRQ1A57bDIzCKK1Qss72kaPDpQpYSfZ1RGy6+c7pqzoC4E3zrOJ6 88 | 4GAiBTyC01Li85xH+DvYskuTVkq/cKs+6WjIHY9YHSpNXic9rQpZL1oRIEDZaARo 89 | LfTAhAsKG3jf7RpY3PtBWm1r8u0c7lwytlzs16YDMqbo3rcoJ1mIgP97rYlY1R4U 90 | pPKwcNSgPqcCAwEAAaOCA4UwggOBMA4GA1UdDwEB/wQEAwIBhjA7BgNVHSUENDAy 91 | BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUH 92 | AwgwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUH 93 | AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o 94 | dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0 95 | AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1 96 | AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp 97 | AGcAaQBDAGUAcgB0ACAARQBWACAAQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl 98 | AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo 99 | AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg 100 | AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg 101 | AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wEgYDVR0TAQH/BAgwBgEB/wIBADCB 102 | gwYIKwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy 103 | dC5jb20wTQYIKwYBBQUHMAKGQWh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NBQ2Vy 104 | dHMvRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3J0MIGPBgNVHR8EgYcw 105 | gYQwQKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hB 106 | c3N1cmFuY2VFVlJvb3RDQS5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0 107 | LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwHQYDVR0OBBYE 108 | FExYyyXwQU9S9CjIgUObpqig5pLlMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoI 109 | Au9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQBMeheHKF0XvLIyc7/NLvVYMR3wsXFU 110 | nNabZ5PbLwM+Fm8eA8lThKNWYB54lBuiqG+jpItSkdfdXJW777UWSemlQk808kf/ 111 | roF/E1S3IMRwFcuBCoHLdFfcnN8kpCkMGPAc5K4HM+zxST5Vz25PDVR708noFUjU 112 | xbvcNRx3RQdIRYW9135TuMAW2ZXNi419yWBP0aKb49Aw1rRzNubS+QOy46T15bg+ 113 | BEkAui6mSnKDcp33C4ypieez12Qf1uNgywPE3IjpnSUBAHHLA7QpYCWP+UbRe3Gu 114 | zVMSW4SOwg/H7ZMZ2cn6j1g0djIvruFQFGHUqFijyDATI+/GJYw2jxyA 115 | -----END CERTIFICATE----- 116 | """ 117 | def test_readpubkey(self): 118 | c=X509(self.cert1) 119 | p=c.pubkey 120 | self.assertEqual(p.exportpub(),self.pubkey1) 121 | def test_pem(self): 122 | c=X509(self.cert1) 123 | self.assertEqual(c.pem(),self.cert1) 124 | def test_subject(self): 125 | c=X509(self.cert1) 126 | self.assertEqual(chartype(c.subject),u'C=RU,ST=Москва,L=Москва,O=Частное лицо,CN=Виктор Вагнер') 127 | def test_subject_str(self): 128 | c=X509(self.cert1) 129 | self.assertEqual(bintype(c.subject),b'C=RU,ST=\\D0\\9C\\D0\\BE\\D1\\81\\D0\\BA\\D0\\B2\\D0\\B0,L=\\D0\\9C\\D0\\BE\\D1\\81\\D0\\BA\\D0\\B2\\D0\\B0,O=\\D0\\A7\\D0\\B0\\D1\\81\\D1\\82\\D0\\BD\\D0\\BE\\D0\\B5 \\D0\\BB\\D0\\B8\\D1\\86\\D0\\BE,CN=\\D0\\92\\D0\\B8\\D0\\BA\\D1\\82\\D0\\BE\\D1\\80 \\D0\\92\\D0\\B0\\D0\\B3\\D0\\BD\\D0\\B5\\D1\\80') 130 | def test_subject_len(self): 131 | c=X509(self.cert1) 132 | self.assertEqual(len(c.subject),5) 133 | def test_issuer(self): 134 | c=X509(self.cert1) 135 | self.assertEqual(chartype(c.issuer),u'O=Удостоверяющий центр,CN=Виктор Вагнер,C=RU,ST=Москва') 136 | def test_subjectfields(self): 137 | c=X509(self.cert1) 138 | self.assertEqual(c.subject[Oid("C")],"RU") 139 | with self.assertRaises(TypeError): 140 | x=c.subject["CN"] 141 | self.assertEqual(c.subject[Oid("L")],u'\u041c\u043e\u0441\u043a\u0432\u0430') 142 | def test_subjectmodify(self): 143 | c=X509(self.cert1) 144 | with self.assertRaises(ValueError): 145 | c.subject[Oid("CN")]=u'Foo' 146 | with self.assertRaises(ValueError): 147 | del c.subject[Oid('CN')] 148 | def test_subjectbadsubfield(self): 149 | c=X509(self.cert1) 150 | with self.assertRaises(KeyError): 151 | x=c.subject[Oid("streetAddress")] 152 | def test_subjectfieldindex(self): 153 | c=X509(self.cert1) 154 | self.assertEqual(repr(c.subject[0]),repr((Oid('C'),u'RU'))) 155 | def test_subjectbadindex(self): 156 | c=X509(self.cert1) 157 | with self.assertRaises(IndexError): 158 | x=c.subject[11] 159 | with self.assertRaises(IndexError): 160 | x=c.subject[-1] 161 | def test_notBefore(self): 162 | c=X509(self.cert1) 163 | self.assertEqual(c.startDate,datetime.datetime(2018,4,29,12,38,6,0,utc)) 164 | def test_notAfter(self): 165 | c=X509(self.cert1) 166 | self.assertEqual(c.endDate,datetime.datetime(2028,4,26,12,38,6,0,utc)) 167 | def test_subjectHash(self): 168 | c=X509(self.cert1) 169 | self.assertEqual(hash(c.subject),0x1f3ed722) 170 | def test_issuerHash(self): 171 | c=X509(self.cert1) 172 | self.assertEqual(hash(c.issuer),0x55224769) 173 | def test_namecomp(self): 174 | c=X509(self.cert1) 175 | ca=X509(self.ca_cert) 176 | self.assertEqual(c.issuer,ca.subject) 177 | self.assertNotEqual(c.subject,c.issuer) 178 | self.assertEqual(ca.issuer,ca.subject) 179 | def test_serial(self): 180 | c=X509(self.cert1) 181 | self.assertEqual(c.serial,int("f941addf6362d979",16)) 182 | def test_version(self): 183 | c=X509(self.cert1) 184 | self.assertEqual(c.version,3) 185 | def test_ca_cert(self): 186 | ca=X509(self.ca_cert) 187 | self.assertTrue(ca.check_ca()) 188 | notca=X509(self.cert1) 189 | self.assertFalse(notca.check_ca()) 190 | def test_extension_count(self): 191 | cert=X509(self.cert1) 192 | self.assertTrue(len(cert.extensions),4) 193 | ca_cert=X509(self.ca_cert) 194 | self.assertEqual(len(ca_cert.extensions),3) 195 | def test_extension_outofrange(self): 196 | cert=X509(self.cert1) 197 | with self.assertRaises(IndexError): 198 | cert.extensions[8] 199 | with self.assertRaises(IndexError): 200 | cert.extensions[-1] 201 | def test_extension_oid(self): 202 | cert=X509(self.cert1) 203 | ext=cert.extensions[0] 204 | ext_id=ext.oid 205 | self.assertTrue(isinstance(ext_id,Oid)) 206 | self.assertEqual(ext_id,Oid('basicConstraints')) 207 | def test_extension_text(self): 208 | cert=X509(self.cert1) 209 | ext=cert.extensions[0] 210 | self.assertEqual(bintype(ext),b'CA:FALSE') 211 | self.assertEqual(chartype(ext),u'CA:FALSE') 212 | def test_extenson_find(self): 213 | cert=X509(self.cert1) 214 | exts=cert.extensions.find(Oid('subjectAltName')) 215 | self.assertEqual(len(exts),1) 216 | self.assertEqual(exts[0].oid,Oid('subjectAltName')) 217 | def test_extension_bad_find(self): 218 | cert=X509(self.cert1) 219 | with self.assertRaises(TypeError): 220 | exts=cert.extensions.find('subjectAltName') 221 | def test_extenson_critical(self): 222 | cert=X509(self.digicert_cert) 223 | crit_exts=cert.extensions.find_critical() 224 | self.assertEqual(len(crit_exts),2) 225 | other_exts=cert.extensions.find_critical(False) 226 | self.assertEqual(len(crit_exts)+len(other_exts),len(cert.extensions)) 227 | self.assertEqual(crit_exts[0].critical,True) 228 | self.assertEqual(other_exts[0].critical,False) 229 | def test_verify_by_key(self): 230 | ca=X509(self.ca_cert) 231 | pubkey=ca.pubkey 232 | self.assertTrue(ca.verify(key=pubkey)) 233 | c=X509(self.cert1) 234 | pk2=c.pubkey 235 | self.assertFalse(c.verify(key=pk2)) 236 | self.assertTrue(c.verify(key=pubkey)) 237 | def test_verify_self_singed(self): 238 | ca=X509(self.ca_cert) 239 | self.assertTrue(ca.verify()) 240 | def test_default_filestore(self): 241 | store=X509Store(default=True) 242 | c1=X509(self.cert1) 243 | # Cert signed by our CA shouldn't be successfully verified 244 | # by default CA store 245 | self.assertFalse(c1.verify(store)) 246 | # but cert, downloaded from some commercial CA - should. 247 | c2=X509(self.digicert_cert) 248 | self.assertTrue(c2.verify(store)) 249 | def test_verify_by_filestore(self): 250 | trusted=NamedTemporaryFile(delete=False,mode="w") 251 | trusted.write(self.ca_cert) 252 | trusted.close() 253 | goodcert=X509(self.cert1) 254 | badcert=X509(self.cert1[0:-30]+"GG"+self.cert1[-28:]) 255 | gitcert=X509(self.digicert_cert) 256 | store=X509Store(file=trusted.name) 257 | os.unlink(trusted.name) 258 | # We should successfuly verify certificate signed by our CA cert 259 | self.assertTrue(goodcert.verify(store)) 260 | # We should reject corrupted certificate 261 | self.assertFalse(badcert.verify(store)) 262 | # And if we specify explicitely certificate file, certificate, 263 | # signed by some commercial CA should be rejected too 264 | self.assertFalse(gitcert.verify(store)) 265 | trusted.close() 266 | def test_verify_by_dirstore(self): 267 | pass 268 | def test_certstack1(self): 269 | l=[] 270 | l.append(X509(self.cert1)) 271 | self.assertEqual(chartype(l[0].subject[Oid('CN')]),u'Виктор Вагнер') 272 | l.append(X509(self.ca_cert)) 273 | l.append(X509(self.digicert_cert)) 274 | stack=StackOfX509(certs=l) 275 | self.assertEqual(len(stack),3) 276 | self.assertTrue(isinstance(stack[1],X509)) 277 | self.assertEqual(chartype(stack[0].subject[Oid('CN')]),u'Виктор Вагнер') 278 | with self.assertRaises(IndexError): 279 | c=stack[-1] 280 | with self.assertRaises(IndexError): 281 | c=stack[3] 282 | del stack[1] 283 | self.assertEqual(len(stack),2) 284 | self.assertEqual(chartype(stack[0].subject[Oid('CN')]),u'Виктор Вагнер') 285 | self.assertEqual(chartype(stack[1].subject[Oid('CN')]),u'DigiCert High Assurance EV CA-1') 286 | def test_certstack2(self): 287 | stack=StackOfX509() 288 | stack.append(X509(self.cert1)) 289 | stack.append(X509(self.ca_cert)) 290 | c=stack[1] 291 | stack[1]=X509(self.digicert_cert) 292 | self.assertEqual(len(stack),2) 293 | self.assertEqual(chartype(stack[1].subject[Oid('CN')]),u'DigiCert High Assurance EV CA-1') 294 | with self.assertRaises(IndexError): 295 | stack[-1]=c 296 | with self.assertRaises(IndexError): 297 | stack[3]=c 298 | with self.assertRaises(TypeError): 299 | stack[0]=self.cert1 300 | with self.assertRaises(TypeError): 301 | stack.append(self.cert1) 302 | def test_certstack3(self): 303 | l=[] 304 | l.append(X509(self.cert1)) 305 | self.assertEqual(chartype(l[0].subject[Oid('CN')]),u'Виктор Вагнер') 306 | l.append(X509(self.ca_cert)) 307 | l.append(X509(self.digicert_cert)) 308 | stack=StackOfX509(certs=l) 309 | stack2=StackOfX509(ptr=stack.ptr,disposable=False) 310 | with self.assertRaises(ValueError): 311 | stack3=StackOfX509(ptr=stack.ptr,certs=l) 312 | with self.assertRaises(ValueError): 313 | stack2[1]=l[0] 314 | with self.assertRaises(ValueError): 315 | stack2.append(l[0]) 316 | if __name__ == '__main__': 317 | unittest.main() 318 | -------------------------------------------------------------------------------- /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, SignedData and EncryptedData objects (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/keys. 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 | Factory function to create CMS objects from received messages. 61 | 62 | Parses CMS data and returns either SignedData or EnvelopedData 63 | object. format argument can be either "PEM" or "DER". 64 | 65 | It determines object type from the contents of received CMS 66 | structure. 67 | """ 68 | bio = Membio(data) 69 | if format == "PEM": 70 | ptr = libcrypto.PEM_read_bio_CMS(bio.bio, None, None, None) 71 | else: 72 | ptr = libcrypto.d2i_CMS_bio(bio.bio, None) 73 | if ptr is None: 74 | raise CMSError("Error parsing CMS data") 75 | typeoid = Oid(libcrypto.OBJ_obj2nid(libcrypto.CMS_get0_type(ptr))) 76 | if typeoid.shortname() == "pkcs7-signedData": 77 | return SignedData(ptr) 78 | elif typeoid.shortname() == "pkcs7-envelopedData": 79 | return EnvelopedData(ptr) 80 | elif typeoid.shortname() == "pkcs7-encryptedData": 81 | return EncryptedData(ptr) 82 | else: 83 | raise NotImplementedError("cannot handle "+typeoid.shortname()) 84 | 85 | class CMSBase(object): 86 | """ 87 | Common ancessor for all CMS types. 88 | Implements serializatio/deserialization 89 | """ 90 | def __init__(self, ptr): 91 | """ 92 | User should never call CMS objects constructors directly, 93 | use create static method or CMS factory function. 94 | """ 95 | if ptr is None: 96 | raise ValueError("Cannot create CMS objects from nothing. Use create static method or CMS factory function") 97 | self.ptr = ptr 98 | def __str__(self): 99 | """ 100 | Serialize in DER format 101 | """ 102 | bio = Membio() 103 | if not libcrypto.i2d_CMS_bio(bio.bio, self.ptr): 104 | raise CMSError("writing CMS to DER") 105 | return str(bio) 106 | 107 | def pem(self): 108 | """ 109 | Serialize in PEM format 110 | """ 111 | bio = Membio() 112 | if not libcrypto.PEM_write_bio_CMS(bio.bio, self.ptr): 113 | raise CMSError("writing CMS to PEM") 114 | return str(bio) 115 | 116 | def __del__(self): 117 | libcrypto.CMS_ContentInfo_free(self.ptr) 118 | 119 | 120 | #pylint: disable=R0921 121 | class SignedData(CMSBase): 122 | """ 123 | Represents signed message (signeddata CMS type) 124 | 125 | Use create static method to create signed message by signing 126 | raw data by your private key. 127 | 128 | Otherwise, if you got this object by parsing message by CMS 129 | function, you can use verify method to check existing signature or 130 | sign to add your own. 131 | 132 | """ 133 | @staticmethod 134 | def create(data, cert, pkey, flags=Flags.BINARY, certs=None): 135 | """ 136 | Creates SignedData message by signing data with pkey and 137 | certificate. 138 | 139 | @param data - data to sign 140 | @param cert - signer's certificate 141 | @param pkey - pkey object with private key to sign 142 | @param flags - OReed combination of Flags constants 143 | @param certs - list of X509 objects to include into CMS 144 | """ 145 | if not pkey.cansign: 146 | raise ValueError("Specified keypair has no private part") 147 | if cert.pubkey != pkey: 148 | raise ValueError("Certificate doesn't match public key") 149 | bio = Membio(data) 150 | if certs is not None and len(certs) > 0: 151 | certstack_obj = StackOfX509(certs) # keep reference to prevent immediate __del__ call 152 | certstack = certstack_obj.ptr 153 | else: 154 | certstack = None 155 | ptr = libcrypto.CMS_sign(cert.cert, pkey.key, certstack, bio.bio, flags) 156 | if ptr is None: 157 | raise CMSError("signing message") 158 | return SignedData(ptr) 159 | def sign(self, cert, pkey, digest_type=None, data=None, flags=Flags.BINARY): 160 | """ 161 | Adds another signer to already signed message 162 | @param cert - signer's certificate 163 | @param pkey - signer's private key 164 | @param digest_type - message digest to use as DigestType object 165 | (if None - default for key would be used) 166 | @param data - data to sign (if detached and 167 | Flags.REUSE_DIGEST is not specified) 168 | @param flags - ORed combination of Flags consants 169 | """ 170 | if not pkey.cansign: 171 | raise ValueError("Specified keypair has no private part") 172 | if cert.pubkey != pkey: 173 | raise ValueError("Certificate doesn't match public key") 174 | if libcrypto.CMS_add1_signer(self.ptr, cert.cert, pkey.key, 175 | digest_type.digest, flags) is None: 176 | raise CMSError("adding signer") 177 | if flags & Flags.REUSE_DIGEST == 0: 178 | if data is not None: 179 | bio = Membio(data) 180 | biodata = bio.bio 181 | else: 182 | biodata = None 183 | res = libcrypto.CMS_final(self.ptr, biodata, None, flags) 184 | if res <= 0: 185 | raise CMSError("Cannot finalize CMS") 186 | def verify(self, store, flags, data=None, certs=None): 187 | """ 188 | Verifies signature under CMS message using trusted cert store 189 | 190 | @param store - X509Store object with trusted certs 191 | @param flags - OR-ed combination of flag consants 192 | @param data - message data, if messge has detached signature 193 | param certs - list of certificates to use during verification 194 | If Flags.NOINTERN is specified, these are only 195 | sertificates to search for signing certificates 196 | @returns True if signature valid, False otherwise 197 | """ 198 | bio = None 199 | if data != None: 200 | bio_obj = Membio(data) 201 | bio = bio_obj.bio 202 | if certs is not None and len(certs) > 0: 203 | certstack_obj = StackOfX509(certs) # keep reference to prevent immediate __del__ call 204 | certstack = certstack_obj.ptr 205 | else: 206 | certstack = None 207 | res = libcrypto.CMS_verify(self.ptr, certstack, store.store, bio, 208 | None, flags) 209 | return res > 0 210 | 211 | @property 212 | def signers(self): 213 | """ 214 | Return list of signer's certificates 215 | """ 216 | signerlist = libcrypto.CMS_get0_signers(self.ptr) 217 | if signerlist is None: 218 | raise CMSError("Cannot get signers") 219 | return StackOfX509(ptr=signerlist, disposable=False) 220 | 221 | @property 222 | def detached(self): 223 | """ 224 | True, if SignedData object represents detached signature, i.e. 225 | just signature without data inside. 226 | """ 227 | return libcrypto.CMS_is_detached(self.ptr) != 0 228 | @property 229 | def data(self): 230 | """ 231 | Returns signed data if present in the message 232 | """ 233 | # Check if signatire is detached 234 | if self.detached: 235 | return None 236 | bio = Membio() 237 | if not libcrypto.CMS_verify(self.ptr, None, None, None, bio.bio, 238 | Flags.NO_VERIFY): 239 | raise CMSError("extract data") 240 | return str(bio) 241 | 242 | def addcert(self, cert): 243 | """ 244 | Adds a certificate (probably intermediate CA) to the SignedData 245 | structure 246 | """ 247 | if libcrypto.CMS_add1_cert(self.ptr, cert.cert) <= 0: 248 | raise CMSError("Cannot add cert") 249 | def addcrl(self, crl): 250 | """ 251 | Adds a CRL to the signed data structure 252 | """ 253 | raise NotImplementedError 254 | @property 255 | def certs(self): 256 | """ 257 | List of the certificates contained in the structure 258 | """ 259 | certstack = libcrypto.CMS_get1_certs(self.ptr) 260 | if certstack is None: 261 | raise CMSError("getting certs") 262 | return StackOfX509(ptr=certstack, disposable=True) 263 | @property 264 | def crls(self): 265 | """ 266 | List of the CRLs contained in the structure 267 | """ 268 | raise NotImplementedError 269 | 270 | class EnvelopedData(CMSBase): 271 | """ 272 | Represents EnvelopedData CMS, i.e. message encrypted with session 273 | keys, encrypted with recipient's public keys 274 | 275 | Use create static method to create such message. All the recipients 276 | certificates should be passed to this method at once, including your 277 | own if you want to be able to decrypt message later yourself. 278 | 279 | Only thing you can do with EnvelopedData object you get by parsing 280 | message with CMS function is to decrypt it. 281 | 282 | """ 283 | @staticmethod 284 | def create(recipients, data, cipher, flags=0): 285 | """ 286 | Creates and encrypts message 287 | @param recipients - list of X509 objects 288 | @param data - contents of the message 289 | @param cipher - CipherType object 290 | @param flags - flag 291 | """ 292 | recp = StackOfX509(recipients) 293 | bio = Membio(data) 294 | cms_ptr = libcrypto.CMS_encrypt(recp.ptr, bio.bio, cipher.cipher, 295 | flags) 296 | if cms_ptr is None: 297 | raise CMSError("encrypt EnvelopedData") 298 | return EnvelopedData(cms_ptr) 299 | 300 | def decrypt(self, pkey, cert, flags=0): 301 | """ 302 | Decrypts message 303 | @param pkey - private key to decrypt 304 | @param cert - certificate of this private key (to find 305 | neccessary RecipientInfo 306 | @param flags - flags 307 | @returns - decrypted data 308 | """ 309 | if not pkey.cansign: 310 | raise ValueError("Specified keypair has no private part") 311 | if pkey != cert.pubkey: 312 | raise ValueError("Certificate doesn't match private key") 313 | bio = Membio() 314 | res = libcrypto.CMS_decrypt(self.ptr, pkey.key, cert.cert, None, 315 | bio.bio, flags) 316 | if res <= 0: 317 | raise CMSError("decrypting CMS") 318 | return str(bio) 319 | 320 | class EncryptedData(CMSBase): 321 | """ 322 | Represents encrypted data CMS structure, i.e. encrypted 323 | with symmetric key, shared by sender and recepient. 324 | """ 325 | @staticmethod 326 | def create(data, cipher, key, flags=0): 327 | """ 328 | Creates an EncryptedData message. 329 | @param data data to encrypt 330 | @param cipher cipher.CipherType object represening required 331 | cipher type 332 | @param key - byte array used as simmetic key 333 | @param flags - OR-ed combination of Flags constant 334 | """ 335 | bio = Membio(data) 336 | ptr = libcrypto.CMS_EncryptedData_encrypt(bio.bio, cipher.cipher, 337 | key, len(key), flags) 338 | if ptr is None: 339 | raise CMSError("encrypt data") 340 | return EncryptedData(ptr) 341 | 342 | def decrypt(self, key, flags=0): 343 | """ 344 | Decrypts encrypted data message 345 | @param key - symmetic key to decrypt 346 | @param flags - OR-ed combination of Flags constant 347 | """ 348 | bio = Membio() 349 | if libcrypto.CMS_EncryptedData_decrypt(self.ptr, key, len(key), None, 350 | bio.bio, flags) <= 0: 351 | raise CMSError("decrypt data") 352 | return str(bio) 353 | 354 | __all__ = ['CMS', 'CMSError', 'Flags', 'SignedData', 'EnvelopedData', 355 | 'EncryptedData'] 356 | 357 | libcrypto.CMS_get0_type.restype = c_void_p 358 | libcrypto.CMS_get0_type.argtypes = (c_void_p,) 359 | libcrypto.CMS_add1_cert.restype = c_int 360 | libcrypto.CMS_add1_cert.argtypes = (c_void_p, c_void_p) 361 | libcrypto.CMS_decrypt.restype = c_int 362 | libcrypto.CMS_decrypt.argtypes = (c_void_p, c_void_p, c_void_p, 363 | c_void_p, c_void_p, c_uint) 364 | libcrypto.CMS_encrypt.restype = c_void_p 365 | libcrypto.CMS_encrypt.argtypes = (c_void_p, c_void_p, c_void_p, c_uint) 366 | libcrypto.CMS_EncryptedData_decrypt.restype = c_int 367 | libcrypto.CMS_EncryptedData_decrypt.argtypes = (c_void_p, c_char_p, c_size_t, 368 | c_void_p, c_void_p, c_uint) 369 | libcrypto.CMS_EncryptedData_encrypt.restype = c_void_p 370 | libcrypto.CMS_EncryptedData_encrypt.argtypes = (c_void_p, c_void_p, c_char_p, 371 | c_size_t, c_uint) 372 | libcrypto.CMS_final.restype = c_int 373 | libcrypto.CMS_final.argtypes = (c_void_p, c_void_p, c_void_p, c_uint) 374 | libcrypto.CMS_get0_signers.restype = c_void_p 375 | libcrypto.CMS_get0_signers.argtypes = (c_void_p, ) 376 | libcrypto.CMS_get1_certs.restype = c_void_p 377 | libcrypto.CMS_get1_certs.argtypes = (c_void_p, ) 378 | libcrypto.CMS_sign.restype = c_void_p 379 | libcrypto.CMS_is_detached.argtypes = (c_void_p,) 380 | libcrypto.CMS_is_detached.restype = c_int 381 | libcrypto.CMS_sign.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p, c_uint) 382 | libcrypto.CMS_add1_signer.restype = c_void_p 383 | libcrypto.CMS_add1_signer.argtypes = (c_void_p, c_void_p, c_void_p, 384 | c_void_p, c_uint) 385 | libcrypto.CMS_verify.restype = c_int 386 | libcrypto.CMS_verify.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p, 387 | c_void_p, c_int) 388 | libcrypto.d2i_CMS_bio.restype = c_void_p 389 | libcrypto.d2i_CMS_bio.argtypes = (c_void_p, POINTER(c_void_p)) 390 | libcrypto.i2d_CMS_bio.restype = c_int 391 | libcrypto.i2d_CMS_bio.argtypes = (c_void_p, c_void_p) 392 | libcrypto.PEM_read_bio_CMS.restype = c_void_p 393 | libcrypto.PEM_read_bio_CMS.argtypes = (c_void_p, POINTER(c_void_p), 394 | c_void_p, c_void_p) 395 | libcrypto.PEM_write_bio_CMS.restype = c_int 396 | libcrypto.PEM_write_bio_CMS.argtypes = (c_void_p, c_void_p) 397 | libcrypto.CMS_ContentInfo_free.argtypes = (c_void_p, ) 398 | -------------------------------------------------------------------------------- /ctypescrypto/pkey.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides interface for low-level private/public keypair operation 3 | 4 | PKey object of this module is wrapper around OpenSSL EVP_PKEY object. 5 | """ 6 | 7 | 8 | from ctypes import c_char, c_char_p, c_void_p, c_int, c_long, POINTER 9 | from ctypes import create_string_buffer, byref, memmove, CFUNCTYPE 10 | from ctypescrypto import libcrypto,pyver,bintype,chartype 11 | from ctypescrypto.exception import LibCryptoError, clear_err_stack 12 | from ctypescrypto.bio import Membio 13 | 14 | __all__ = ['PKeyError', 'PKey', 'PW_CALLBACK_FUNC'] 15 | class PKeyError(LibCryptoError): 16 | """ Exception thrown if libcrypto finctions return an error """ 17 | pass 18 | 19 | PW_CALLBACK_FUNC = CFUNCTYPE(c_int, POINTER(c_char), c_int, c_int, c_char_p) 20 | """ Function type for pem password callback """ 21 | 22 | def _password_callback(c): 23 | """ 24 | Converts given user function or string to C password callback 25 | function, passable to openssl. 26 | 27 | IF function is passed, it would be called upon reading or writing 28 | PEM format private key with one argument which is True if we are 29 | writing key and should verify passphrase and false if we are reading 30 | 31 | """ 32 | if c is None: 33 | return PW_CALLBACK_FUNC(0) 34 | if callable(c): 35 | if pyver ==2 : 36 | def __cb(buf, length, rwflag, userdata): 37 | pwd = c(rwflag) 38 | cnt = min(len(pwd),length) 39 | memmove(buf,pwd, cnt) 40 | return cnt 41 | else: 42 | def __cb(buf, length, rwflag, userdata): 43 | pwd = c(rwflag).encode("utf-8") 44 | cnt = min(len(pwd),length) 45 | memmove(buf,pwd, cnt) 46 | return cnt 47 | else: 48 | if pyver > 2: 49 | c=c.encode("utf-8") 50 | def __cb(buf,length,rwflag,userdata): 51 | cnt=min(len(c),length) 52 | memmove(buf,c,cnt) 53 | return cnt 54 | return PW_CALLBACK_FUNC(__cb) 55 | 56 | def _keybio(blob, format): 57 | # But DER string should be binary 58 | if format == "PEM" and isinstance(blob,chartype): 59 | return Membio(blob.encode("ascii"),clone=True) 60 | elif isinstance(blob,bintype): 61 | return Membio(blob) 62 | else: 63 | raise TypeError("Key should be either blob or PEM string") 64 | 65 | class PKey(object): 66 | """ 67 | Represents public/private key pair. Wrapper around EVP_PKEY 68 | libcrypto object. 69 | 70 | May contain either both private and public key (such objects can be 71 | used for signing, deriving shared key as well as verifying or public 72 | key only, which can be used for verifying or as peer key when 73 | deriving. 74 | 75 | @var cansign is true key has private part. 76 | @var key contain pointer to EVP_PKEY and should be passed to various 77 | libcrypto routines 78 | """ 79 | def __init__(self, ptr=None, privkey=None, pubkey=None, format="PEM", 80 | cansign=False, password=None): 81 | """ 82 | PKey object can be created from either private/public key blob or 83 | from C language pointer, returned by some OpenSSL function 84 | 85 | Following named arguments are recognized by constructor 86 | 87 | privkey - private key blob. If this is specified, format and 88 | password can be also specified 89 | 90 | pubkey - public key blob. If this is specified, format can be 91 | specified. 92 | 93 | ptr - pointer, returned by openssl function. If it is specified, 94 | cansign should be also specified. 95 | 96 | These three arguments are mutually exclusive. 97 | 98 | format - can be either 'PEM' or 'DER'. Specifies format of blob. 99 | 100 | password - can be string with password for encrypted key, or 101 | callable with one boolean argument, which returns password. 102 | During constructor call this argument would be false. 103 | 104 | If key is in PEM format, its encrypted status and format is 105 | autodetected. If key is in DER format, than if password is 106 | specified, key is assumed to be encrypted PKCS8 key otherwise 107 | it is assumed to be unencrypted. 108 | """ 109 | 110 | if not ptr is None: 111 | self.key = ptr 112 | self.cansign = cansign 113 | if not privkey is None or not pubkey is None: 114 | raise TypeError("Just one of ptr, pubkey or privkey can " + 115 | "be specified") 116 | elif not privkey is None: 117 | if not pubkey is None: 118 | raise TypeError("Just one of ptr, pubkey or privkey can " + 119 | "be specified") 120 | bio=_keybio(privkey,format) 121 | self.cansign = True 122 | if format == "PEM": 123 | self.key = libcrypto.PEM_read_bio_PrivateKey(bio.bio, None, 124 | _password_callback(password), 125 | None) 126 | else: 127 | if password is not None: 128 | self.key = libcrypto.d2i_PKCS8PrivateKey_bio(bio.bio,None, 129 | _password_callback(password), 130 | None) 131 | else: 132 | self.key = libcrypto.d2i_PrivateKey_bio(bio.bio, None) 133 | if self.key is None: 134 | raise PKeyError("error parsing private key") 135 | elif not pubkey is None: 136 | bio = _keybio(pubkey,format) 137 | self.cansign = False 138 | if format == "PEM": 139 | self.key = libcrypto.PEM_read_bio_PUBKEY(bio.bio, None, 140 | _password_callback(password), 141 | None) 142 | else: 143 | self.key = libcrypto.d2i_PUBKEY_bio(bio.bio, None) 144 | if self.key is None: 145 | raise PKeyError("error parsing public key") 146 | else: 147 | raise TypeError("Neither public, nor private key is specified") 148 | 149 | 150 | def __del__(self): 151 | """ Frees EVP_PKEY object (note, it is reference counted) """ 152 | if hasattr(self,"key"): 153 | libcrypto.EVP_PKEY_free(self.key) 154 | 155 | def __eq__(self, other): 156 | """ Compares two public keys. If one has private key and other 157 | doesn't it doesn't affect result of comparation 158 | """ 159 | return libcrypto.EVP_PKEY_cmp(self.key, other.key) == 1 160 | 161 | def __ne__(self, other): 162 | """ Compares two public key for not-equality """ 163 | return not self.__eq__(other) 164 | 165 | def __str__(self): 166 | """ printable representation of public key """ 167 | bio = Membio() 168 | libcrypto.EVP_PKEY_print_public(bio.bio, self.key, 0, None) 169 | return str(bio) 170 | 171 | def sign(self, digest, **kwargs): 172 | """ 173 | Signs given digest and retirns signature 174 | Keyword arguments allows to set various algorithm-specific 175 | parameters. See pkeyutl(1) manual. 176 | """ 177 | ctx = libcrypto.EVP_PKEY_CTX_new(self.key, None) 178 | if ctx is None: 179 | raise PKeyError("Initailizing sign context") 180 | if libcrypto.EVP_PKEY_sign_init(ctx) < 1: 181 | raise PKeyError("sign_init") 182 | self._configure_context(ctx, kwargs) 183 | # Find out signature size 184 | siglen = c_long(0) 185 | if libcrypto.EVP_PKEY_sign(ctx, None, byref(siglen), digest, 186 | len(digest)) < 1: 187 | raise PKeyError("computing signature length") 188 | sig = create_string_buffer(siglen.value) 189 | if libcrypto.EVP_PKEY_sign(ctx, sig, byref(siglen), digest, 190 | len(digest)) < 1: 191 | raise PKeyError("signing") 192 | libcrypto.EVP_PKEY_CTX_free(ctx) 193 | return sig.raw[:int(siglen.value)] 194 | 195 | def verify(self, digest, signature, **kwargs): 196 | """ 197 | Verifies given signature on given digest 198 | Returns True if Ok, False if don't match 199 | Keyword arguments allows to set algorithm-specific 200 | parameters 201 | """ 202 | ctx = libcrypto.EVP_PKEY_CTX_new(self.key, None) 203 | if ctx is None: 204 | raise PKeyError("Initailizing verify context") 205 | if libcrypto.EVP_PKEY_verify_init(ctx) < 1: 206 | raise PKeyError("verify_init") 207 | self._configure_context(ctx, kwargs) 208 | ret = libcrypto.EVP_PKEY_verify(ctx, signature, len(signature), digest, 209 | len(digest)) 210 | if ret < 0: 211 | raise PKeyError("Signature verification") 212 | libcrypto.EVP_PKEY_CTX_free(ctx) 213 | return ret > 0 214 | 215 | def derive(self, peerkey, **kwargs): 216 | """ 217 | Derives shared key (DH,ECDH,VKO 34.10). Requires 218 | private key available 219 | 220 | @param peerkey - other key (may be public only) 221 | 222 | Keyword parameters are algorithm-specific 223 | """ 224 | if not self.cansign: 225 | raise ValueError("No private key available") 226 | ctx = libcrypto.EVP_PKEY_CTX_new(self.key, None) 227 | if ctx is None: 228 | raise PKeyError("Initailizing derive context") 229 | if libcrypto.EVP_PKEY_derive_init(ctx) < 1: 230 | raise PKeyError("derive_init") 231 | 232 | # This is workaround around missing functionality in GOST engine 233 | # it provides only numeric control command to set UKM, not 234 | # string one. 235 | self._configure_context(ctx, kwargs, ["ukm"]) 236 | if libcrypto.EVP_PKEY_derive_set_peer(ctx, peerkey.key) <= 0: 237 | raise PKeyError("Cannot set peer key") 238 | if "ukm" in kwargs: 239 | # We just hardcode numeric command to set UKM here 240 | if libcrypto.EVP_PKEY_CTX_ctrl(ctx, -1, 1 << 10, 8, 8, 241 | kwargs["ukm"]) <= 0: 242 | raise PKeyError("Cannot set UKM") 243 | keylen = c_long(0) 244 | if libcrypto.EVP_PKEY_derive(ctx, None, byref(keylen)) <= 0: 245 | raise PKeyError("computing shared key length") 246 | buf = create_string_buffer(keylen.value) 247 | if libcrypto.EVP_PKEY_derive(ctx, buf, byref(keylen)) <= 0: 248 | raise PKeyError("computing actual shared key") 249 | libcrypto.EVP_PKEY_CTX_free(ctx) 250 | return buf.raw[:int(keylen.value)] 251 | 252 | @staticmethod 253 | def generate(algorithm, **kwargs): 254 | """ 255 | Generates new private-public key pair for given algorithm 256 | (string like 'rsa','ec','gost2001') and algorithm-specific 257 | parameters. 258 | 259 | Algorithm specific paramteers for RSA: 260 | 261 | rsa_keygen_bits=number - size of key to be generated 262 | rsa_keygen_pubexp - RSA public expontent(default 65537) 263 | 264 | Algorithm specific parameters for DSA,DH and EC 265 | 266 | paramsfrom=PKey object 267 | 268 | copy parameters of newly generated key from existing key 269 | 270 | Algorithm specific parameters for GOST2001 271 | 272 | paramset= paramset name where name is one of 273 | 'A','B','C','XA','XB','test' 274 | 275 | paramsfrom does work too 276 | """ 277 | tmpeng = c_void_p(None) 278 | if isinstance(algorithm, chartype): 279 | alg = algorithm.encode("ascii") 280 | else: 281 | alg = algorithm 282 | ameth = libcrypto.EVP_PKEY_asn1_find_str(byref(tmpeng), alg, -1) 283 | if ameth is None: 284 | raise PKeyError("Algorithm %s not foind\n"%(algorithm)) 285 | clear_err_stack() 286 | pkey_id = c_int(0) 287 | libcrypto.EVP_PKEY_asn1_get0_info(byref(pkey_id), None, None, None, 288 | None, ameth) 289 | #libcrypto.ENGINE_finish(tmpeng) 290 | if "paramsfrom" in kwargs: 291 | ctx = libcrypto.EVP_PKEY_CTX_new(kwargs["paramsfrom"].key, None) 292 | else: 293 | ctx = libcrypto.EVP_PKEY_CTX_new_id(pkey_id, None) 294 | # FIXME support EC curve as keyword param by invoking paramgen 295 | # operation 296 | if ctx is None: 297 | raise PKeyError("Creating context for key type %d"%(pkey_id.value)) 298 | if libcrypto.EVP_PKEY_keygen_init(ctx) <= 0: 299 | raise PKeyError("keygen_init") 300 | PKey._configure_context(ctx, kwargs, ["paramsfrom"]) 301 | key = c_void_p(None) 302 | if libcrypto.EVP_PKEY_keygen(ctx, byref(key)) <= 0: 303 | raise PKeyError("Error generating key") 304 | libcrypto.EVP_PKEY_CTX_free(ctx) 305 | return PKey(ptr=key, cansign=True) 306 | 307 | def exportpub(self, format="PEM"): 308 | """ 309 | Returns public key as PEM or DER structure. 310 | """ 311 | bio = Membio() 312 | if format == "PEM": 313 | retcode = libcrypto.PEM_write_bio_PUBKEY(bio.bio, self.key) 314 | else: 315 | retcode = libcrypto.i2d_PUBKEY_bio(bio.bio, self.key) 316 | if retcode == 0: 317 | raise PKeyError("error serializing public key") 318 | return str(bio) 319 | 320 | def exportpriv(self, format="PEM", password=None, cipher=None): 321 | """ 322 | Returns private key as PEM or DER Structure. 323 | If password and cipher are specified, encrypts key 324 | on given password, using given algorithm. Cipher must be 325 | an ctypescrypto.cipher.CipherType object 326 | 327 | Password can be either string or function with one argument, 328 | which returns password. It is called with argument True, which 329 | means, that we are encrypting key, and password should be 330 | verified (requested twice from user, for example). 331 | """ 332 | bio = Membio() 333 | if cipher is None: 334 | evp_cipher = None 335 | else: 336 | evp_cipher = cipher.cipher 337 | if format == "PEM": 338 | ret = libcrypto.PEM_write_bio_PrivateKey(bio.bio, self.key, 339 | evp_cipher, None, 0, 340 | _password_callback(password), 341 | None) 342 | if ret ==0: 343 | raise PKeyError("error serializing private key") 344 | return str(bio) 345 | else: 346 | ret = libcrypto.i2d_PKCS8PrivateKey_bio(bio.bio, self.key, 347 | evp_cipher, None, 0, 348 | _password_callback(password), 349 | None) 350 | if ret ==0: 351 | raise PKeyError("error serializing private key") 352 | return bintype(bio) 353 | 354 | @staticmethod 355 | def _configure_context(ctx, opts, skip=()): 356 | """ 357 | Configures context of public key operations 358 | @param ctx - context to configure 359 | @param opts - dictionary of options (from kwargs of calling 360 | function) 361 | @param skip - list of options which shouldn't be passed to 362 | context 363 | """ 364 | 365 | for oper in opts: 366 | if oper in skip: 367 | continue 368 | if isinstance(oper,chartype): 369 | op = oper.encode("ascii") 370 | else: 371 | op = oper 372 | if isinstance(opts[oper],chartype): 373 | value = opts[oper].encode("ascii") 374 | elif isinstance(opts[oper],bintype): 375 | value = opts[oper] 376 | else: 377 | if pyver == 2: 378 | value = str(opts[oper]) 379 | else: 380 | value = str(opts[oper]).encode('ascii') 381 | ret = libcrypto.EVP_PKEY_CTX_ctrl_str(ctx, op, value) 382 | if ret == -2: 383 | raise PKeyError("Parameter %s is not supported by key" % oper) 384 | if ret < 1: 385 | raise PKeyError("Error setting parameter %s" % oper) 386 | # Declare function prototypes 387 | libcrypto.EVP_PKEY_cmp.argtypes = (c_void_p, c_void_p) 388 | libcrypto.PEM_read_bio_PrivateKey.restype = c_void_p 389 | libcrypto.PEM_read_bio_PrivateKey.argtypes = (c_void_p, POINTER(c_void_p), 390 | PW_CALLBACK_FUNC, c_char_p) 391 | libcrypto.PEM_read_bio_PUBKEY.restype = c_void_p 392 | libcrypto.PEM_read_bio_PUBKEY.argtypes = (c_void_p, POINTER(c_void_p), 393 | PW_CALLBACK_FUNC, c_char_p) 394 | libcrypto.d2i_PUBKEY_bio.restype = c_void_p 395 | libcrypto.d2i_PUBKEY_bio.argtypes = (c_void_p, c_void_p) 396 | libcrypto.d2i_PrivateKey_bio.restype = c_void_p 397 | libcrypto.d2i_PrivateKey_bio.argtypes = (c_void_p, c_void_p) 398 | libcrypto.EVP_PKEY_print_public.argtypes = (c_void_p, c_void_p, c_int, c_void_p) 399 | libcrypto.EVP_PKEY_asn1_find_str.restype = c_void_p 400 | libcrypto.EVP_PKEY_asn1_find_str.argtypes = (c_void_p, c_char_p, c_int) 401 | libcrypto.EVP_PKEY_asn1_get0_info.restype = c_int 402 | libcrypto.EVP_PKEY_asn1_get0_info.argtypes = (POINTER(c_int), POINTER(c_int), 403 | POINTER(c_int), POINTER(c_char_p), 404 | POINTER(c_char_p), c_void_p) 405 | libcrypto.EVP_PKEY_cmp.restype = c_int 406 | libcrypto.EVP_PKEY_cmp.argtypes = (c_void_p, c_void_p) 407 | libcrypto.EVP_PKEY_CTX_ctrl_str.restype = c_int 408 | libcrypto.EVP_PKEY_CTX_ctrl_str.argtypes = (c_void_p, c_void_p, c_void_p) 409 | libcrypto.EVP_PKEY_CTX_ctrl.restype = c_int 410 | libcrypto.EVP_PKEY_CTX_ctrl.argtypes = (c_void_p, c_int, c_int, c_int, c_int, 411 | c_void_p) 412 | libcrypto.EVP_PKEY_CTX_free.argtypes = (c_void_p, ) 413 | libcrypto.EVP_PKEY_CTX_new.restype = c_void_p 414 | libcrypto.EVP_PKEY_CTX_new.argtypes = (c_void_p, c_void_p) 415 | libcrypto.EVP_PKEY_CTX_new_id.restype = c_void_p 416 | libcrypto.EVP_PKEY_CTX_new_id.argtypes = (c_int, c_void_p) 417 | libcrypto.EVP_PKEY_derive.restype = c_int 418 | libcrypto.EVP_PKEY_derive.argtypes = (c_void_p, c_char_p, POINTER(c_long)) 419 | libcrypto.EVP_PKEY_derive_init.restype = c_int 420 | libcrypto.EVP_PKEY_derive_init.argtypes = (c_void_p, ) 421 | libcrypto.EVP_PKEY_derive_set_peer.restype = c_int 422 | libcrypto.EVP_PKEY_derive_set_peer.argtypes = (c_void_p, c_void_p) 423 | libcrypto.EVP_PKEY_free.argtypes = (c_void_p,) 424 | libcrypto.EVP_PKEY_keygen.restype = c_int 425 | libcrypto.EVP_PKEY_keygen.argtypes = (c_void_p, c_void_p) 426 | libcrypto.EVP_PKEY_keygen_init.restype = c_int 427 | libcrypto.EVP_PKEY_keygen_init.argtypes = (c_void_p, ) 428 | libcrypto.EVP_PKEY_sign.restype = c_int 429 | libcrypto.EVP_PKEY_sign.argtypes = (c_void_p, c_char_p, POINTER(c_long), 430 | c_char_p, c_long) 431 | libcrypto.EVP_PKEY_sign_init.restype = c_int 432 | libcrypto.EVP_PKEY_sign_init.argtypes = (c_void_p, ) 433 | libcrypto.EVP_PKEY_verify.restype = c_int 434 | libcrypto.EVP_PKEY_verify.argtypes = (c_void_p, c_char_p, c_long, c_char_p, 435 | c_long) 436 | libcrypto.EVP_PKEY_verify_init.restype = c_int 437 | libcrypto.EVP_PKEY_verify_init.argtypes = (c_void_p, ) 438 | libcrypto.PEM_write_bio_PrivateKey.argtypes = (c_void_p, c_void_p, c_void_p, 439 | c_char_p, c_int, 440 | PW_CALLBACK_FUNC, c_char_p) 441 | libcrypto.PEM_write_bio_PUBKEY.argtypes = (c_void_p, c_void_p) 442 | libcrypto.i2d_PUBKEY_bio.argtypes = (c_void_p, c_void_p) 443 | libcrypto.i2d_PKCS8PrivateKey_bio.argtypes = (c_void_p, c_void_p, c_void_p, 444 | c_char_p, c_int, 445 | PW_CALLBACK_FUNC, c_char_p) 446 | libcrypto.d2i_PKCS8PrivateKey_bio.restype = c_void_p 447 | libcrypto.d2i_PKCS8PrivateKey_bio.argtypes = (c_void_p,c_void_p, 448 | PW_CALLBACK_FUNC,c_void_p) 449 | libcrypto.ENGINE_finish.argtypes = (c_void_p, ) 450 | -------------------------------------------------------------------------------- /ctypescrypto/x509.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements interface to openssl X509 and X509Store structures, 3 | I.e allows to load, analyze and verify certificates. 4 | 5 | X509Store objects are also used to verify other signed documets, 6 | such as CMS, OCSP and timestamps. 7 | """ 8 | 9 | 10 | 11 | from ctypes import c_void_p, c_long, c_ulong, c_int, POINTER, c_char_p, Structure, cast 12 | from ctypescrypto.bio import Membio 13 | from ctypescrypto.pkey import PKey 14 | from ctypescrypto.oid import Oid 15 | from ctypescrypto.exception import LibCryptoError 16 | from ctypescrypto import libcrypto, pyver, chartype, inttype, bintype 17 | from datetime import datetime 18 | import sys 19 | try: 20 | from pytz import utc 21 | except ImportError: 22 | from datetime import timedelta, tzinfo 23 | ZERO = timedelta(0) 24 | class UTC(tzinfo): 25 | """tzinfo object for UTC. 26 | If no pytz is available, we would use it. 27 | """ 28 | def utcoffset(self, dt): 29 | return ZERO 30 | 31 | def tzname(self, dt): 32 | return "UTC" 33 | 34 | def dst(self, dt): 35 | return ZERO 36 | 37 | utc = UTC() 38 | 39 | __all__ = ['X509', 'X509Error', 'X509Name', 'X509Store', 'StackOfX509'] 40 | 41 | if hasattr(libcrypto,"X509_get_version"): 42 | 43 | # If it is OpenSSL 1.1 or above, use accessor functions 44 | _X509_get_version = libcrypto.X509_get_version 45 | _X509_get_version.restype = c_long 46 | _X509_get_version.argtypes = (c_void_p,) 47 | 48 | _X509_get_notBefore=libcrypto.X509_getm_notBefore 49 | _X509_get_notBefore.restype = c_void_p 50 | _X509_get_notBefore.argtypes = (c_void_p,) 51 | 52 | _X509_get_notAfter=libcrypto.X509_getm_notAfter 53 | _X509_get_notAfter.restype = c_void_p 54 | _X509_get_notAfter.argtypes = (c_void_p,) 55 | else: 56 | # Otherwise declare X509 structure internals and define deep poke 57 | # functions 58 | class _validity(Structure): 59 | """ ctypes representation of X509_VAL structure 60 | needed to access certificate validity period, because openssl 61 | doesn't provide fuctions for it - only macros 62 | """ 63 | _fields_ = [('notBefore', c_void_p), ('notAfter', c_void_p)] 64 | 65 | class _cinf(Structure): 66 | """ ctypes representtion of X509_CINF structure 67 | neede to access certificate data, which are accessable only 68 | via macros 69 | """ 70 | _fields_ = [('version', c_void_p), 71 | ('serialNumber', c_void_p), 72 | ('sign_alg', c_void_p), 73 | ('issuer', c_void_p), 74 | ('validity', POINTER(_validity)), 75 | ('subject', c_void_p), 76 | ('pubkey', c_void_p), 77 | ('issuerUID', c_void_p), 78 | ('subjectUID', c_void_p), 79 | ('extensions', c_void_p), 80 | ] 81 | 82 | class _x509(Structure): 83 | """ 84 | ctypes represntation of X509 structure needed 85 | to access certificate data which are accesable only via 86 | macros, not functions 87 | """ 88 | _fields_ = [('cert_info', POINTER(_cinf)), 89 | ('sig_alg', c_void_p), 90 | ('signature', c_void_p), 91 | # There are a lot of parsed extension fields there 92 | ] 93 | _px509 = POINTER(_x509) 94 | def _X509_get_version(ptr): 95 | asn1int = cast(ptr, _px509)[0].cert_info[0].version 96 | return libcrypto.ASN1_INTEGER_get(asn1int) 97 | 98 | def _X509_get_notBefore(ptr): 99 | # (x)->cert_info->validity->notBefore 100 | return cast(ptr, _px509)[0].cert_info[0].validity[0].notBefore 101 | def _X509_get_notAfter(ptr): 102 | return cast(ptr, _px509)[0].cert_info[0].validity[0].notAfter 103 | 104 | if hasattr(libcrypto,'sk_num'): 105 | sk_num = libcrypto.sk_num 106 | sk_set = libcrypto.sk_set 107 | sk_value = libcrypto.sk_value 108 | sk_delete = libcrypto.sk_delete 109 | sk_new_null = libcrypto.sk_new_null 110 | sk_pop_free = libcrypto.sk_pop_free 111 | sk_push = libcrypto.sk_push 112 | else: 113 | sk_num = libcrypto.OPENSSL_sk_num 114 | sk_set = libcrypto.OPENSSL_sk_set 115 | sk_value = libcrypto.OPENSSL_sk_value 116 | sk_delete = libcrypto.OPENSSL_sk_delete 117 | sk_new_null = libcrypto.OPENSSL_sk_new_null 118 | sk_pop_free = libcrypto.OPENSSL_sk_pop_free 119 | sk_push = libcrypto.OPENSSL_sk_push 120 | class X509Error(LibCryptoError): 121 | """ 122 | Exception, generated when some openssl function fail 123 | during X509 operation 124 | """ 125 | pass 126 | 127 | 128 | class X509Name(object): 129 | """ 130 | Class which represents X.509 distinguished name - typically 131 | a certificate subject name or an issuer name. 132 | 133 | Now used only to represent information, extracted from the 134 | certificate. Potentially can be also used to build DN when creating 135 | certificate signing request 136 | """ 137 | # XN_FLAG_SEP_COMMA_PLUS & ASN1_STRFLG_UTF8_CONVERT 138 | PRINT_FLAG = 0x10010 139 | ESC_MSB = 4 140 | def __init__(self, ptr=None, copy=False): 141 | """ 142 | Creates a X509Name object 143 | @param ptr - pointer to X509_NAME C structure (as returned by some 144 | OpenSSL functions 145 | @param copy - indicates that this structure have to be freed upon 146 | object destruction 147 | """ 148 | if ptr is not None: 149 | self.ptr = ptr 150 | self.need_free = copy 151 | self.writable = False 152 | else: 153 | self.ptr = libcrypto.X509_NAME_new() 154 | self.need_free = True 155 | self.writable = True 156 | 157 | def __del__(self): 158 | """ 159 | Frees if neccessary 160 | """ 161 | if self.need_free: 162 | libcrypto.X509_NAME_free(self.ptr) 163 | def __bytes__(self): 164 | """ 165 | Produces an ascii representation of the name, escaping all 166 | symbols > 0x80. Probably it is not what you want, unless 167 | your native language is English 168 | """ 169 | bio = Membio() 170 | libcrypto.X509_NAME_print_ex(bio.bio, self.ptr, 0, 171 | self.PRINT_FLAG | self.ESC_MSB) 172 | return bio.__bytes__() 173 | 174 | def __unicode__(self): 175 | """ 176 | Produces unicode representation of the name. 177 | """ 178 | bio = Membio() 179 | libcrypto.X509_NAME_print_ex(bio.bio, self.ptr, 0, self.PRINT_FLAG) 180 | return bio.__unicode__() 181 | if pyver == 2: 182 | __str__ = __bytes__ 183 | else: 184 | __str__ = __unicode__ 185 | 186 | 187 | def __len__(self): 188 | """ 189 | return number of components in the name 190 | """ 191 | return libcrypto.X509_NAME_entry_count(self.ptr) 192 | def __cmp__(self, other): 193 | """ 194 | Compares X509 names 195 | """ 196 | return libcrypto.X509_NAME_cmp(self.ptr, other.ptr) 197 | def __eq__(self, other): 198 | return libcrypto.X509_NAME_cmp(self.ptr, other.ptr) == 0 199 | def __gt__(self, other): 200 | return libcrypto.X509_NAME_cmp(self.ptr, other.ptr) > 0 201 | def __lt__(self, other): 202 | return libcrypto.X509_NAME_cmp(self.ptr, other.ptr) < 0 203 | 204 | def __getitem__(self, key): 205 | if isinstance(key, Oid): 206 | # Return first matching field 207 | idx = libcrypto.X509_NAME_get_index_by_NID(self.ptr, key.nid, -1) 208 | if idx < 0: 209 | raise KeyError("Key not found " + str(Oid)) 210 | entry = libcrypto.X509_NAME_get_entry(self.ptr, idx) 211 | value = libcrypto.X509_NAME_ENTRY_get_data(entry) 212 | bio = Membio() 213 | libcrypto.ASN1_STRING_print_ex(bio.bio, value, self.PRINT_FLAG) 214 | return chartype(bio) 215 | elif isinstance(key, inttype): 216 | # Return OID, string tuple 217 | entry = libcrypto.X509_NAME_get_entry(self.ptr, key) 218 | if entry is None: 219 | raise IndexError("name entry index out of range") 220 | oid = Oid.fromobj(libcrypto.X509_NAME_ENTRY_get_object(entry)) 221 | value = libcrypto.X509_NAME_ENTRY_get_data(entry) 222 | bio = Membio() 223 | libcrypto.ASN1_STRING_print_ex(bio.bio, value, self.PRINT_FLAG) 224 | return (oid, chartype(bio)) 225 | else: 226 | raise TypeError("X509 NAME can be indexed by Oids or integers only") 227 | 228 | def __setitem__(self, key, val): 229 | if not self.writable: 230 | raise ValueError("Attempt to modify constant X509 object") 231 | else: 232 | raise NotImplementedError 233 | def __delitem__(self, key): 234 | if not self.writable: 235 | raise ValueError("Attempt to modify constant X509 object") 236 | else: 237 | raise NotImplementedError 238 | def __hash__(self): 239 | return libcrypto.X509_NAME_hash(self.ptr) 240 | 241 | class _x509_ext(Structure): 242 | """ Represens C structure X509_EXTENSION """ 243 | _fields_ = [("object", c_void_p), 244 | ("critical", c_int), 245 | ("value", c_void_p) 246 | ] 247 | 248 | class X509_EXT(object): 249 | """ Python object which represents a certificate extension """ 250 | def __init__(self, ptr, copy=False): 251 | """ Initializes from the pointer to X509_EXTENSION. 252 | If copy is True, creates a copy, otherwise just 253 | stores pointer. 254 | """ 255 | if copy: 256 | self.ptr = libcrypto.X509_EXTENSION_dup(ptr) 257 | else: 258 | self.ptr = cast(ptr, POINTER(_x509_ext)) 259 | def __del__(self): 260 | libcrypto.X509_EXTENSION_free(self.ptr) 261 | def __bytes__(self): 262 | bio = Membio() 263 | libcrypto.X509V3_EXT_print(bio.bio, self.ptr, 0x20010, 0) 264 | return bintype(bio) 265 | def __unicode__(self): 266 | bio = Membio() 267 | libcrypto.X509V3_EXT_print(bio.bio, self.ptr, 0x20010, 0) 268 | return chartype(bio) 269 | if pyver == 2: 270 | __str__ = __bytes__ 271 | else: 272 | __str__ = __unicode__ 273 | @property 274 | def oid(self): 275 | "Returns OID of the extension" 276 | return Oid.fromobj(self.ptr[0].object) 277 | @property 278 | def critical(self): 279 | "Returns True if extensin have critical flag set" 280 | return self.ptr[0].critical > 0 281 | 282 | class _X509extlist(object): 283 | """ 284 | Represents list of certificate extensions. Really it keeps 285 | reference to certificate object 286 | """ 287 | def __init__(self, cert): 288 | """ 289 | Initialize from X509 object 290 | """ 291 | self.cert = cert 292 | 293 | def __len__(self): 294 | """ 295 | Returns number of extensions 296 | """ 297 | return libcrypto.X509_get_ext_count(self.cert.cert) 298 | 299 | def __getitem__(self, item): 300 | """ 301 | Returns extension by index, creating a copy 302 | """ 303 | ext_ptr = libcrypto.X509_get_ext(self.cert.cert, item) 304 | if ext_ptr is None: 305 | raise IndexError 306 | return X509_EXT(ext_ptr, True) 307 | def find(self, oid): 308 | """ 309 | Return list of extensions with given Oid 310 | """ 311 | if not isinstance(oid, Oid): 312 | raise TypeError("Need crytypescrypto.oid.Oid as argument") 313 | found = [] 314 | index = -1 315 | end = len(self) 316 | while True: 317 | index = libcrypto.X509_get_ext_by_NID(self.cert.cert, oid.nid, 318 | index) 319 | if index >= end or index < 0: 320 | break 321 | found.append(self[index]) 322 | return found 323 | 324 | def find_critical(self, crit=True): 325 | """ 326 | Return list of critical extensions (or list of non-cricital, if 327 | optional second argument is False 328 | """ 329 | if crit: 330 | flag = 1 331 | else: 332 | flag = 0 333 | found = [] 334 | end = len(self) 335 | index = -1 336 | while True: 337 | index = libcrypto.X509_get_ext_by_critical(self.cert.cert, flag, 338 | index) 339 | if index >= end or index < 0: 340 | break 341 | found.append(self[index]) 342 | return found 343 | 344 | def _X509__asn1date_to_datetime(asn1date): 345 | """ 346 | Converts openssl ASN1_TIME object to python datetime.datetime 347 | """ 348 | bio = Membio() 349 | libcrypto.ASN1_TIME_print(bio.bio, asn1date) 350 | pydate = datetime.strptime(str(bio), "%b %d %H:%M:%S %Y %Z") 351 | return pydate.replace(tzinfo=utc) 352 | 353 | class X509(object): 354 | """ 355 | Represents X.509 certificate. 356 | """ 357 | def __init__(self, data=None, ptr=None, format="PEM"): 358 | """ 359 | Initializes certificate 360 | @param data - serialized certificate in PEM or DER format. 361 | @param ptr - pointer to X509, returned by some openssl function. 362 | mutually exclusive with data 363 | @param format - specifies data format. "PEM" or "DER", default PEM 364 | """ 365 | if ptr is not None: 366 | if data is not None: 367 | raise TypeError("Cannot use data and ptr simultaneously") 368 | self.cert = ptr 369 | elif data is None: 370 | raise TypeError("data argument is required") 371 | else: 372 | bio = Membio(data) 373 | if format == "PEM": 374 | self.cert = libcrypto.PEM_read_bio_X509(bio.bio, None, None, 375 | None) 376 | else: 377 | self.cert = libcrypto.d2i_X509_bio(bio.bio, None) 378 | if self.cert is None: 379 | raise X509Error("error reading certificate") 380 | self.extensions = _X509extlist(self) 381 | def __del__(self): 382 | """ 383 | Frees certificate object 384 | """ 385 | libcrypto.X509_free(self.cert) 386 | def __bytes__(self): 387 | """ Returns der string of the certificate """ 388 | bio = Membio() 389 | if libcrypto.i2d_X509_bio(bio.bio, self.cert) == 0: 390 | raise X509Error("error serializing certificate") 391 | return str(bio) 392 | if pyver == 2: 393 | __str__ = __bytes__ 394 | def __repr__(self): 395 | """ Returns valid call to the constructor """ 396 | return "X509(data=" + repr(self.pem()) + ",format='PEM')" 397 | @property 398 | def pubkey(self): 399 | """EVP PKEy object of certificate public key""" 400 | return PKey(ptr=libcrypto.X509_get_pubkey(self.cert, False)) 401 | def pem(self): 402 | """ Returns PEM represntation of the certificate """ 403 | bio = Membio() 404 | if libcrypto.PEM_write_bio_X509(bio.bio, self.cert) == 0: 405 | raise X509Error("error serializing certificate") 406 | return str(bio) 407 | def verify(self, store=None, chain=None, key=None): 408 | """ 409 | Verify self. Supports verification on both X509 store object 410 | or just public issuer key 411 | @param store X509Store object. 412 | @param chain - list of X509 objects to add into verification 413 | context.These objects are untrusted, but can be used to 414 | build certificate chain up to trusted object in the store 415 | @param key - PKey object with open key to validate signature 416 | 417 | parameters store and key are mutually exclusive. If neither 418 | is specified, attempts to verify self as self-signed certificate 419 | """ 420 | if store is not None and key is not None: 421 | raise X509Error("key and store cannot be specified simultaneously") 422 | if store is not None: 423 | ctx = libcrypto.X509_STORE_CTX_new() 424 | if ctx is None: 425 | raise X509Error("Error allocating X509_STORE_CTX") 426 | if chain is not None and len(chain) > 0: 427 | chain_ptr = StackOfX509(chain).ptr 428 | else: 429 | chain_ptr = None 430 | if libcrypto.X509_STORE_CTX_init(ctx, store.store, self.cert, 431 | chain_ptr) < 0: 432 | raise X509Error("Error allocating X509_STORE_CTX") 433 | res = libcrypto.X509_verify_cert(ctx) 434 | libcrypto.X509_STORE_CTX_free(ctx) 435 | return res > 0 436 | else: 437 | if key is None: 438 | if self.issuer != self.subject: 439 | # Not a self-signed certificate 440 | return False 441 | key = self.pubkey 442 | res = libcrypto.X509_verify(self.cert, key.key) 443 | if res < 0: 444 | raise X509Error("X509_verify failed") 445 | return res > 0 446 | 447 | @property 448 | def subject(self): 449 | """ X509Name for certificate subject name """ 450 | return X509Name(libcrypto.X509_get_subject_name(self.cert)) 451 | @property 452 | def issuer(self): 453 | """ X509Name for certificate issuer name """ 454 | return X509Name(libcrypto.X509_get_issuer_name(self.cert)) 455 | @property 456 | def serial(self): 457 | """ Serial number of certificate as integer """ 458 | asnint = libcrypto.X509_get_serialNumber(self.cert) 459 | bio = Membio() 460 | libcrypto.i2a_ASN1_INTEGER(bio.bio, asnint) 461 | return int(str(bio), 16) 462 | @property 463 | def version(self): 464 | """ 465 | certificate version as integer. Really certificate stores 0 for 466 | version 1 and 2 for version 3, but we return 1 and 3 467 | """ 468 | return _X509_get_version(self.cert) + 1 469 | @property 470 | def startDate(self): 471 | """ Certificate validity period start date """ 472 | asn1 = _X509_get_notBefore(self.cert) 473 | return __asn1date_to_datetime(asn1) 474 | @property 475 | def endDate(self): 476 | """ Certificate validity period end date """ 477 | asn1 = _X509_get_notAfter(self.cert) 478 | return __asn1date_to_datetime(asn1) 479 | def check_ca(self): 480 | """ Returns True if certificate is CA certificate """ 481 | return libcrypto.X509_check_ca(self.cert) > 0 482 | 483 | class X509Store(object): 484 | """ 485 | Represents trusted certificate store. Can be used to lookup CA 486 | certificates to verify 487 | 488 | @param file - file with several certificates and crls 489 | to load into store 490 | @param dir - hashed directory with certificates and crls 491 | @param default - if true, default verify location (directory) 492 | is installed 493 | 494 | """ 495 | def __init__(self, file=None, dir=None, default=False): 496 | """ 497 | Creates X509 store and installs lookup method. Optionally initializes 498 | by certificates from given file or directory. 499 | """ 500 | # 501 | # Todo - set verification flags 502 | # 503 | self.store = libcrypto.X509_STORE_new() 504 | if self.store is None: 505 | raise X509Error("allocating store") 506 | lookup = libcrypto.X509_STORE_add_lookup(self.store, 507 | libcrypto.X509_LOOKUP_file()) 508 | if lookup is None: 509 | raise X509Error("error installing file lookup method") 510 | if file is not None: 511 | if pyver == 2: 512 | fn = file 513 | else: 514 | fn = file.encode(sys.getfilesystemencoding()) 515 | if not libcrypto.X509_LOOKUP_ctrl(lookup, 1, fn, 1, None) > 0: 516 | raise X509Error("error loading trusted certs from file "+file) 517 | lookup = libcrypto.X509_STORE_add_lookup(self.store, 518 | libcrypto.X509_LOOKUP_hash_dir()) 519 | if lookup is None: 520 | raise X509Error("error installing hashed lookup method") 521 | if dir is not None: 522 | if pyver == 2: 523 | dr = dir 524 | else: 525 | dr = dir.encode(sys.getfilesystemencoding()) 526 | if not libcrypto.X509_LOOKUP_ctrl(lookup, 2, dr, 1, None) > 0: 527 | raise X509Error("error adding hashed trusted certs dir "+dir) 528 | if default: 529 | if not libcrypto.X509_LOOKUP_ctrl(lookup, 2, None, 3, None) > 0: 530 | raise X509Error("error adding default trusted certs dir ") 531 | def add_cert(self, cert): 532 | """ 533 | Explicitely adds certificate to set of trusted in the store 534 | @param cert - X509 object to add 535 | """ 536 | if not isinstance(cert, X509): 537 | raise TypeError("cert should be X509") 538 | libcrypto.X509_STORE_add_cert(self.store, cert.cert) 539 | def add_callback(self, callback): 540 | """ 541 | Installs callback function, which would receive detailed information 542 | about verified ceritificates 543 | """ 544 | raise NotImplementedError 545 | def setflags(self, flags): 546 | """ 547 | Set certificate verification flags. 548 | @param flags - integer bit mask. See OpenSSL X509_V_FLAG_* constants 549 | """ 550 | libcrypto.X509_STORE_set_flags(self.store, flags) 551 | def setpurpose(self, purpose): 552 | """ 553 | Sets certificate purpose which verified certificate should match 554 | @param purpose - number from 1 to 9 or standard strind defined 555 | in Openssl 556 | possible strings - sslcient,sslserver, nssslserver, smimesign,i 557 | smimeencrypt, crlsign, any, ocsphelper 558 | """ 559 | if isinstance(purpose, str): 560 | purp_no = libcrypto.X509_PURPOSE_get_by_sname(purpose) 561 | if purp_no <= 0: 562 | raise X509Error("Invalid certificate purpose '%s'" % purpose) 563 | elif isinstance(purpose, int): 564 | purp_no = purpose 565 | if libcrypto.X509_STORE_set_purpose(self.store, purp_no) <= 0: 566 | raise X509Error("cannot set purpose") 567 | def setdepth(self, depth): 568 | """ 569 | Sets the verification depth i.e. max length of certificate chain 570 | which is acceptable 571 | """ 572 | libcrypto.X509_STORE_set_depth(self.store, depth) 573 | def settime(self, time): 574 | """ 575 | Set point in time used to check validity of certificates for 576 | Time can be either python datetime object or number of seconds 577 | sinse epoch 578 | """ 579 | if isinstance(time, datetime) or isinstance(time, 580 | datetime.date): 581 | seconds = int(time.strftime("%s")) 582 | elif isinstance(time, int): 583 | seconds = time 584 | else: 585 | raise TypeError("datetime.date, datetime.datetime or integer " + 586 | "is required as time argument") 587 | raise NotImplementedError 588 | class StackOfX509(object): 589 | """ 590 | Implements OpenSSL STACK_OF(X509) object. 591 | It looks much like python container types 592 | """ 593 | def __init__(self, certs=None, ptr=None, disposable=True): 594 | """ 595 | Create stack 596 | @param certs - list of X509 objects. If specified, read-write 597 | stack is created and populated by these certificates 598 | @param ptr - pointer to OpenSSL STACK_OF(X509) as returned by 599 | some functions 600 | @param disposable - if True, stack created from object, returned 601 | by function is copy, and can be modified and need to be 602 | freeid. If false, it is just pointer into another 603 | structure i.e. CMS_ContentInfo 604 | """ 605 | self.need_free = False 606 | if ptr is None: 607 | self.need_free = True 608 | self.ptr = sk_new_null() 609 | if certs is not None: 610 | for crt in certs: 611 | self.append(crt) 612 | elif certs is not None: 613 | raise ValueError("cannot handle certs an ptr simultaneously") 614 | else: 615 | self.need_free = disposable 616 | self.ptr = ptr 617 | def __len__(self): 618 | return sk_num(self.ptr) 619 | def __getitem__(self, index): 620 | if index < 0 or index >= len(self): 621 | raise IndexError 622 | p = sk_value(self.ptr, index) 623 | return X509(ptr=libcrypto.X509_dup(p)) 624 | def __setitem__(self, index, value): 625 | if not self.need_free: 626 | raise ValueError("Stack is read-only") 627 | if index < 0 or index >= len(self): 628 | raise IndexError 629 | if not isinstance(value, X509): 630 | raise TypeError('StackOfX509 can contain only X509 objects') 631 | p = sk_value(self.ptr, index) 632 | sk_set(self.ptr, index, libcrypto.X509_dup(value.cert)) 633 | libcrypto.X509_free(p) 634 | def __delitem__(self, index): 635 | if not self.need_free: 636 | raise ValueError("Stack is read-only") 637 | if index < 0 or index >= len(self): 638 | raise IndexError 639 | p = sk_delete(self.ptr, index) 640 | libcrypto.X509_free(p) 641 | def __del__(self): 642 | if self.need_free: 643 | sk_pop_free(self.ptr, libcrypto.X509_free) 644 | def append(self, value): 645 | """ Adds certificate to stack """ 646 | if not self.need_free: 647 | raise ValueError("Stack is read-only") 648 | if not isinstance(value, X509): 649 | raise TypeError('StackOfX509 can contain only X509 objects') 650 | sk_push(self.ptr, libcrypto.X509_dup(value.cert)) 651 | 652 | libcrypto.d2i_X509_bio.argtypes = (c_void_p,POINTER(c_void_p)) 653 | libcrypto.X509_free.argtypes = (c_void_p,) 654 | libcrypto.X509_dup.restype = c_void_p 655 | libcrypto.X509_dup.argtypes = (c_void_p, ) 656 | libcrypto.i2a_ASN1_INTEGER.argtypes = (c_void_p, c_void_p) 657 | libcrypto.ASN1_STRING_print_ex.argtypes = (c_void_p, c_void_p, c_long) 658 | libcrypto.PEM_read_bio_X509.restype = c_void_p 659 | libcrypto.PEM_read_bio_X509.argtypes = (c_void_p, POINTER(c_void_p), 660 | c_void_p, c_void_p) 661 | libcrypto.PEM_write_bio_X509.restype = c_int 662 | libcrypto.PEM_write_bio_X509.argtypes = (c_void_p, c_void_p) 663 | libcrypto.ASN1_TIME_print.argtypes = (c_void_p, c_void_p) 664 | libcrypto.ASN1_INTEGER_get.argtypes = (c_void_p, ) 665 | libcrypto.ASN1_INTEGER_get.restype = c_long 666 | libcrypto.X509_check_ca.argtypes = (c_void_p, ) 667 | libcrypto.X509_get_serialNumber.argtypes = (c_void_p, ) 668 | libcrypto.X509_get_serialNumber.restype = c_void_p 669 | libcrypto.X509_get_subject_name.argtypes = (c_void_p, ) 670 | libcrypto.X509_get_subject_name.restype = c_void_p 671 | libcrypto.X509_get_issuer_name.argtypes = (c_void_p, ) 672 | libcrypto.X509_get_issuer_name.restype = c_void_p 673 | libcrypto.X509_NAME_ENTRY_get_object.restype = c_void_p 674 | libcrypto.X509_NAME_ENTRY_get_object.argtypes = (c_void_p, ) 675 | libcrypto.X509_NAME_ENTRY_get_data.restype = c_void_p 676 | libcrypto.X509_NAME_ENTRY_get_data.argtypes = (c_void_p, ) 677 | libcrypto.OBJ_obj2nid.argtypes = (c_void_p, ) 678 | libcrypto.X509_NAME_get_entry.restype = c_void_p 679 | libcrypto.X509_NAME_get_entry.argtypes = (c_void_p, c_int) 680 | libcrypto.X509_STORE_new.restype = c_void_p 681 | libcrypto.X509_STORE_add_lookup.restype = c_void_p 682 | libcrypto.X509_STORE_add_lookup.argtypes = (c_void_p, c_void_p) 683 | libcrypto.X509_STORE_add_cert.argtypes = (c_void_p, c_void_p) 684 | libcrypto.X509_STORE_CTX_new.restype = c_void_p 685 | libcrypto.X509_STORE_CTX_free.argtypes = (c_void_p,) 686 | libcrypto.X509_STORE_CTX_init.argtypes = (c_void_p, c_void_p, c_void_p, 687 | c_void_p) 688 | libcrypto.X509_STORE_set_depth.argtypes = (c_void_p, c_int) 689 | libcrypto.X509_STORE_set_flags.argtypes = (c_void_p, c_ulong) 690 | libcrypto.X509_STORE_set_purpose.argtypes = (c_void_p, c_int) 691 | libcrypto.X509_LOOKUP_file.restype = c_void_p 692 | libcrypto.X509_LOOKUP_hash_dir.restype = c_void_p 693 | libcrypto.X509_LOOKUP_ctrl.restype = c_int 694 | libcrypto.X509_LOOKUP_ctrl.argtypes = (c_void_p, c_int, c_char_p, c_long, 695 | POINTER(c_char_p)) 696 | libcrypto.X509_EXTENSION_free.argtypes = (c_void_p, ) 697 | libcrypto.X509_EXTENSION_dup.argtypes = (c_void_p, ) 698 | libcrypto.X509_EXTENSION_dup.restype = POINTER(_x509_ext) 699 | libcrypto.X509V3_EXT_print.argtypes = (c_void_p, POINTER(_x509_ext), c_long, 700 | c_int) 701 | libcrypto.X509_get_ext.restype = c_void_p 702 | libcrypto.X509_get_ext.argtypes = (c_void_p, c_int) 703 | libcrypto.X509_get_ext_by_critical.argtypes = (c_void_p, c_int, c_int) 704 | libcrypto.X509_get_ext_by_NID.argtypes = (c_void_p, c_int, c_int) 705 | libcrypto.X509_get_ext_count.argtypes = (c_void_p, ) 706 | libcrypto.X509_get_pubkey.restype = c_void_p 707 | libcrypto.X509_get_pubkey.argtypes = (c_void_p, ) 708 | libcrypto.X509V3_EXT_print.argtypes = (c_void_p, POINTER(_x509_ext), c_long, 709 | c_int) 710 | libcrypto.X509_LOOKUP_file.restype = c_void_p 711 | libcrypto.X509_LOOKUP_hash_dir.restype = c_void_p 712 | libcrypto.X509_NAME_cmp.argtypes = (c_void_p, c_void_p) 713 | libcrypto.X509_NAME_entry_count.argtypes = (c_void_p,) 714 | libcrypto.X509_NAME_free.argtypes = (c_void_p,) 715 | libcrypto.X509_NAME_new.restype = c_void_p 716 | libcrypto.X509_NAME_print_ex.argtypes = (c_void_p, c_void_p, c_int, c_ulong) 717 | libcrypto.X509_PURPOSE_get_by_sname.argtypes=(c_char_p,) 718 | libcrypto.X509_verify.argtypes = (c_void_p, c_void_p) 719 | libcrypto.X509_verify_cert.argtypes = (c_void_p,) 720 | sk_num.restype = c_int 721 | sk_num.argtypes= (c_void_p,) 722 | sk_set.argtypes = (c_void_p, c_int, c_void_p) 723 | sk_set.restype = c_void_p 724 | sk_value.argtypes = (c_void_p, c_int) 725 | sk_value.restype = c_void_p 726 | sk_delete.argtypes = (c_void_p, c_int) 727 | sk_delete.restype = c_void_p 728 | sk_new_null.restype = c_void_p 729 | sk_pop_free.argtypes = (c_void_p, c_void_p) 730 | sk_push.argtypes = (c_void_p, c_void_p) 731 | libcrypto.X509_NAME_hash.restype = c_long 732 | libcrypto.X509_NAME_hash.argtypes = (c_void_p, ) 733 | libcrypto.X509_NAME_get_index_by_NID.argtypes = (c_void_p, c_int, c_int) 734 | --------------------------------------------------------------------------------