]> www.wagner.pp.ru Git - oss/ctypescrypto.git/commitdiff
Added tests for ec key creation from raw material and for private key
authorVictor Wagner <vitus@wagner.pp.ru>
Sun, 26 Oct 2014 11:43:56 +0000 (14:43 +0300)
committerVictor Wagner <vitus@wagner.pp.ru>
Sun, 26 Oct 2014 11:44:10 +0000 (14:44 +0300)
serialization.

Added skeleton for cms module (never run)

ctypescrypto/cms.py [new file with mode: 0644]
ctypescrypto/digest.py
ctypescrypto/ec.py
ctypescrypto/pkey.py
tests/testec.py [new file with mode: 0644]
tests/testpkey.py

diff --git a/ctypescrypto/cms.py b/ctypescrypto/cms.py
new file mode 100644 (file)
index 0000000..9defb1d
--- /dev/null
@@ -0,0 +1,231 @@
+"""
+Implements operations with CMS EnvelopedData and SignedData messages
+
+Contains function CMS() which parses CMS message and creates either
+EnvelopedData or SignedData objects (EncryptedData and CompressedData
+can be easily added, because OpenSSL contain nessesary function)
+
+Each of these objects contains create() static method which is used to
+create it from raw data and neccessary certificates.
+
+
+"""
+
+from ctypescrypto.exception import LibCryptoError
+from ctypescrypto.bio import Membio
+from ctypescrypto.oid import Oid
+
+class CMSError(LibCryptoError):
+       """
+       Exception which is raised when error occurs
+       """
+       pass
+
+class Flags:
+       """
+       Constants for flags passed to the CMS methods. 
+       Can be OR-ed together
+       """
+       TEXT=1
+       NOCERTS=2
+       NO_CONTENT_VERIFY=4
+       NO_ATTR_VERIFY=8
+       NO_SIGS=NO_CONTENT_VERIFY|NO_ATTR_VERIFY
+       NOINTERN=0x10
+       NO_SIGNER_CERT_VERIFY=0x20
+       NO_VERIFY=0x20
+       DETACHED=0x40
+       BINARY=0x80
+       NOATTR=0x100
+       NOSMIMECAP =0x200
+       NOOLDMIMETYPE=0x400
+       CRLFEOL=0x800
+       STREAM=0x1000
+       NOCRL=0x2000
+       PARTIAL=0x4000
+       REUSE_DIGEST=0x8000
+       USE_KEYID=0x10000
+       DEBUG_DECRYPT=0x20000
+
+def CMS(data,format="PEM"):
+       """
+       Parses CMS data and returns either SignedData or EnvelopedData
+       object
+       """
+       b=Membio(data)
+       if format == "PEM":
+               ptr=libcrypto.PEM_read_bio_CMS(b.bio,None,None,None)
+       else:
+               ptr=libcrypto.d2i_CMS_bio(b.bio,None)
+       typeoid = Oid(libcrypto.OBJ_obj2nid(libcrypto.CMS_get0_type(ptr)))
+       if typeoid.shortname()=="pkcs7-signedData":
+               return SignedData(ptr)
+       elif typeoid.shortname()=="pkcs7-envelopedData":
+               return EnvelopedData(ptr)
+       else:
+               raise NotImplementedError("cannot handle "+typeoid.shortname())
+
+
+       
+               
+class SignedData:
+       def __init__(self,ptr=None):
+               self.ptr=ptr
+       @staticmethod
+       def create(data,cert,pkey,flags=Flags.BINARY):
+               """
+                       Creates SignedData message by signing data with pkey and
+                       certificate.
+
+                       @param data - data to sign
+                       @param pkey - pkey object with private key to sign
+               """
+               if not pkey.cansign:
+                       raise ValueError("Specified keypair has no private part")
+               if cert.pubkey!=pkey:
+                       raise ValueError("Certificate doesn't match public key")
+               b=Membio(data)
+               ptr=libcrypto.CMS_sign(cert.cert,pkey.ptr,None,b.bio,flags)
+               if ptr is None:
+                       raise CMSError("signing message")
+               return SignedData(ptr)
+       def sign(self,cert,pkey,md=None,data=None,flags=Flags.BINARY):
+               """
+                       Adds another signer to already signed message
+                       @param cert - signer's certificate
+                       @param pkey - signer's private key
+                       @param data - data to sign (if detached)
+                       @param md - message digest to use as DigestType object (if None - default for key
+                               would be used)
+                       @param flags - flags
+               """
+               if not pkey.cansign:
+                       raise ValueError("Specified keypair has no private part")
+               if cert.pubkey!=pkey:
+                       raise ValueError("Certificate doesn't match public key")
+               p1=libcrypto.CMS_sign_add1_Signer(self.ptr,cert.cert,pkey.ptr,
+                       md.digest,flags)
+               if p1 is None:
+                       raise CMSError("adding signer")
+               if flags & Flags.REUSE_DIGEST==0:
+                       if data is not None:
+                               b=Membio(data)
+                               biodata=b.bio
+                       else:
+                               biodata=None
+                       res= libcrypto.CMS_final(self.ptr,biodata,None,flags)
+                       if res<=0:
+                               raise CMSError
+       def verify(self,store,flags,data=None):
+               bio=None
+               if data!=None:
+                       b=Membio(data)
+                       bio=b.bio
+               res=libcrypto.CMS_verify(self.ptr,store.store,bio,None,flags)
+               return res>0
+       def __str__(self):
+               """
+               Serialize in DER format
+               """
+               b=Membio()
+               if not libcrypto.i2d_CMS_bio(b.bio,self.ptr):
+                       raise CMSError("writing CMS to PEM")
+               return str(b)
+
+       def pem(self):
+               """
+               Serialize in PEM format
+               """
+               b=Membio()
+               if not libcrypto.PEM_write_bio_CMS(b.bio,self.ptr):
+                       raise CMSError("writing CMS to PEM")
+               return str(b)
+               
+       @property       
+       def signers(self,store=None):
+               """
+               Return list of signer's certificates
+               """
+               raise NotImplementedError
+       @property
+       def data(self):
+               """
+               Returns signed data if present
+               """
+               raise NotImplementedError
+       def addcert(self,cert):
+               """
+               Adds a certificate (probably intermediate CA) to the SignedData
+               structure
+               """
+               raise NotImplementedError
+       def addcrl(self,crl):
+               """
+               Adds a CRL to the signed data structure
+               """
+               raise NotImplementedError
+       @property
+       def certs(self):
+               """
+               List of the certificates contained in the structure
+               """
+               raise NotImplementedError
+       @property
+       def crls(self):
+               """
+               List of the CRLs contained in the structure
+               """
+               raise NotImplementedError
+
+class EnvelopedData:
+       def __init__(self,ptr):
+               """
+               Initializes an object. For internal use
+               """
+               self.ptr=ptr
+       def __str__(self):
+               """
+               Serialize in DER format
+               """
+               b=Membio()
+               if not libcrypto.i2d_CMS_bio(b.bio,self.ptr):
+                       raise CMSError("writing CMS to PEM")
+               return str(b)
+
+       def pem(self):
+               """
+               Serialize in PEM format
+               """
+               b=Membio()
+               if not libcrypto.PEM_write_bio_CMS(b.bio,self.ptr):
+                       raise CMSError("writing CMS to PEM")
+               return str(b)
+       @staticmethod
+       def create(recipients,data,cipher,flags=0):
+               """
+               Creates and encrypts message
+               @param recipients - list of X509 objects
+               @param data - contents of the message
+               @param cipher - CipherType object
+               @param flags - flag
+               """
+               # Need to be able to handle OPENSSL stacks
+               raise NotImplementedError       
+       def decrypt(self,pkey,cert,flags=0):
+               """
+               Decrypts message
+               @param pkey - private key to decrypt
+               @param cert - certificate of this private key (to find
+                       neccessary RecipientInfo
+               @param flags - flags
+               @returns - decrypted data
+               """
+               if not pkey.cansign:
+                       raise ValueError("Specified keypair has no private part")
+               if pkey != cert.pubkey:
+                       raise ValueError("Certificate doesn't match private key")
+               b=Membio()
+               res=libcrypto.CMS_decrypt(self.ptr,pkey.ptr,cert.ccert,None,b.bio,flags)
+               if res<=0:
+                       raise CMSError("decrypting CMS")
+               return str(b)
index 4e33d92bb3e147b28a05fd22dd18ef315078dff2..df4e58071a13c98e96b05e56710e86141407bd5b 100644 (file)
@@ -1,5 +1,6 @@
 """\r
-       Implmenets interface to OpenSSL EVP_Digest* functions.\r
+       Implements interface to OpenSSL EVP_Digest* functions.\r
+\r
        Interface  made as close to hashlib as possible.\r
 \r
        This module is really an excess effort. Hashlib allows access to\r
index d325212c7781d196b1d416575436a5e1845005fe..3d880cb6a5c174ec671b24e42de555ec11680dab 100644 (file)
@@ -1,33 +1,74 @@
 """
 Support for EC keypair operation missing form public libcrypto API
 """
+from ctypescrypto.pkey import PKey, PKeyError
+from ctypes import c_void_p,c_char_p,c_int,byref
+from ctypescrypto import libcrypto
 
-
-def create(curve,num):
+def create(curve,data):
        """
                Creates EC keypair from the just secret key and curve name
                
                @param curve - name of elliptic curve
                @param num - long number representing key
        """
-       p=libcrypto.EVP_PKEY_new()
-       ec=libcrypto.EC_KEY_new_by_curvename(curve.nid)
+       ec=libcrypto.EC_KEY_new_by_curve_name(curve.nid)
+       if ec is None:  
+               raise PKeyError("EC_KEY_new_by_curvename")
        group=libcrypto.EC_KEY_get0_group(ec)
-       EC_KEY_set_private_key(ec,bn)
-       priv_key=libcrypt.BN_new()
-       ctx=BN_CTX_new()
-       h="%x"%(num)
-       libcrypto.BN_hex2bn(byref(priv_key),h)
-       libcrypto.EC_KEY_set_private_key(ec,priv_key)
+       if group is None:
+               raise PKeyError("EC_KEY_get0_group")
+       libcrypto.EC_GROUP_set_asn1_flag(group,1)
+       raw_key=libcrypto.BN_new()
+       if raw_key is None:
+               raise PKeyError("BN_new")
+       ctx=libcrypto.BN_CTX_new()
+       if ctx is None:
+               raise PKeyError("BN_CTX_new")
+       if libcrypto.BN_bin2bn(data,len(data),raw_key) is None:
+               raise PKeyError("BN_bin2bn")
+       order=libcrypto.BN_new()
+       if order is None:
+               raise PKeyError("BN_new")
+       priv_key = libcrypto.BN_new()
+       if priv_key is None:
+               raise PKeyError("BN_new")
+       if libcrypto.EC_GROUP_get_order(group,order,ctx) <=0:
+               raise PKeyError("EC_GROUP_get_order")
+       if libcrypto.BN_nnmod(priv_key,raw_key,order,ctx) <=0:
+               raise PKeyError("BN_nnmod")
+       if libcrypto.EC_KEY_set_private_key(ec,priv_key)<=0:
+               raise PKeyError("EC_KEY_set_private_key")
        pub_key=libcrypto.EC_POINT_new(group)
-       libcrypto.EC_POINT_mul(group,pub_key,priv_key,None,None,ctx)
-       libcrypto.BN_free(a)
-       libcrypto.EVP_PKEY_set1_EC_KEY(p,ec)
+       if pub_key is None:
+               raise PKeyError("EC_POINT_new")
+       if libcrypto.EC_POINT_mul(group,pub_key,priv_key,None,None,ctx)<=0:
+               raise PKeyError("EC_POINT_mul")
+       if libcrypto.EC_KEY_set_public_key(ec,pub_key)<=0:
+               raise PKeyError("EC_KEY_set_public_key")
+       libcrypto.BN_free(raw_key)
+       libcrypto.BN_free(order)
+       libcrypto.BN_free(priv_key)
+       libcrypto.BN_CTX_free(ctx)
+       p=libcrypto.EVP_PKEY_new()
+       if p is None:
+               raise PKeyError("EVP_PKEY_new") 
+       if libcrypto.EVP_PKEY_set1_EC_KEY(p,ec)<=0:
+               raise PKeyError("EVP_PKEY_set1_EC_KEY")
        libcrypto.EC_KEY_free(ec)
        return PKey(ptr=p,cansign=True)
 
 
 libcrypto.EVP_PKEY_new.restype=c_void_p
 libcrypto.BN_new.restype=c_void_p
-libcrypto.BN_hex2bn.argtypes(POINTER(c_void_p),c_char_p)
-libcrypto.EC_KEY_set_private_key
+libcrypto.BN_free.argtypes=(c_void_p,)
+libcrypto.BN_CTX_new.restype=c_void_p
+libcrypto.BN_CTX_free.argtypes=(c_void_p,)
+libcrypto.BN_bin2bn.argtypes=(c_char_p,c_int,c_void_p)
+libcrypto.EC_KEY_set_private_key.argtypes=(c_void_p,c_void_p)
+libcrypto.EC_POINT_new.argtypes=(c_void_p,)
+libcrypto.EC_POINT_new.restype=c_void_p
+libcrypto.EC_POINT_mul.argtypes=(c_void_p,c_void_p,c_void_p,c_void_p,c_void_p,c_void_p)
+libcrypto.EC_KEY_set_public_key.argtypes=(c_void_p,c_void_p)
+
+
index f011443a2173ede8058e21c4323ba2967462ace2..10366ee0b318e1947b4448a1be4f9419884af5b3 100644 (file)
@@ -1,10 +1,9 @@
 """
-low-level private/public keypair operation
+This module provides interface for low-level private/public keypair operation
 
 PKey object of this module is wrapper around OpenSSL EVP_PKEY object.
 """
 
-This module provides interface for 
 
 from ctypes import c_char_p,c_void_p,byref,c_int,c_long, c_longlong, create_string_buffer,CFUNCTYPE,POINTER
 from ctypescrypto import libcrypto
@@ -16,11 +15,11 @@ class PKeyError(LibCryptoError):
 
 CALLBACK_FUNC=CFUNCTYPE(c_int,c_char_p,c_int,c_int,c_char_p)
 def password_callback(buf,length,rwflag,u):
-"""
-Example password callback for private key. Assumes that 
-password is store in the userdata parameter, so allows to pass password
-from constructor arguments to the libcrypto keyloading functions
-"""
+       """
+       Example password callback for private key. Assumes that 
+       password is store in the userdata parameter, so allows to pass password
+       from constructor arguments to the libcrypto keyloading functions
+       """
        cnt=len(u)
        if length<cnt:
                cnt=length
@@ -202,7 +201,7 @@ class PKey:
                return str(b)
        def exportpriv(self,format="PEM",password=None,cipher=None):
                """
-                       Returns public key as PEM or DER Structure.
+                       Returns private key as PEM or DER Structure.
                        If password and cipher are specified, encrypts key
                        on given password, using given algorithm. Cipher must be
                        an ctypescrypto.cipher.CipherType object
@@ -215,7 +214,7 @@ class PKey:
                                raise NotImplementedError("Interactive password entry is not supported")
                        evp_cipher=cipher.cipher
                if format == "PEM":
-                       r=libcrypto.PEM_write_bio_PrivateKey(b.bio,self.key,evp_cipher,_cb,
+                       r=libcrypto.PEM_write_bio_PrivateKey(b.bio,self.key,evp_cipher,None,0,_cb,
                                password)
                else:
                        if cipher is not None:
@@ -286,7 +285,7 @@ libcrypto.EVP_PKEY_verify.restype=c_int
 libcrypto.EVP_PKEY_verify.argtypes=(c_void_p,c_char_p,c_long,c_char_p,c_long)
 libcrypto.EVP_PKEY_verify_init.restype=c_int
 libcrypto.EVP_PKEY_verify_init.argtypes=(c_void_p,)
-libcrypto.PEM_write_bio_PrivateKey.argtypes=(c_void_p,c_void_p,CALLBACK_FUNC,c_char_p)
+libcrypto.PEM_write_bio_PrivateKey.argtypes=(c_void_p,c_void_p,c_void_p,c_char_p,c_int,CALLBACK_FUNC,c_char_p)
 libcrypto.PEM_write_bio_PUBKEY.argtypes=(c_void_p,c_void_p)
 libcrypto.i2d_PUBKEY_bio.argtypes=(c_void_p,c_void_p)
 libcrypto.i2d_PrivateKey_bio.argtypes=(c_void_p,c_void_p)
diff --git a/tests/testec.py b/tests/testec.py
new file mode 100644 (file)
index 0000000..496a747
--- /dev/null
@@ -0,0 +1,32 @@
+from ctypescrypto.oid import Oid
+from ctypescrypto.ec import create
+from base64 import b16decode
+import unittest
+
+
+
+class TestEcCreation(unittest.TestCase):
+       ec1priv="""-----BEGIN PRIVATE KEY-----
+MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgKnG6neqZvB98EEuuxnHs
+fv+L/5abuNNG20wzUqRpncOhRANCAARWKXWeUZ6WiCKZ2kHx87jmJyx0G3ZB1iQC
++Gp2AJYswbQPhGPigKolzIbZYfwnn7QOca6N8QDhPAn3QQK8trZI
+-----END PRIVATE KEY-----
+"""
+       bigkey="""-----BEGIN PRIVATE KEY-----
+MHUCAQAwEAYHKoZIzj0CAQYFK4EEAAoEXjBcAgEBBBEBRVEjGVC3X8RALaFzL8m+
+vqFEA0IABJFmwom5+QXlX549+fadfzVrSiIJX4lPRxVxSqS1Zgav8YHrlmvkrLXP
++eFrZtgJvpTiFPBsk/0JEJmvmEmSVec=
+-----END PRIVATE KEY-----
+"""
+       def test_keyone(self):
+               key=create(Oid("secp256k1"),b16decode("2A71BA9DEA99BC1F7C104BAEC671EC7EFF8BFF969BB8D346DB4C3352A4699DC3",True))
+                       
+               out=key.exportpriv()
+               self.assertEqual(out,self.ec1priv)
+
+       def test_bignum(self):
+               keyval='\xff'*32
+               key=create(Oid("secp256k1"),keyval)
+               self.assertEqual(key.exportpriv(),self.bigkey)
+if __name__ == "__main__":
+       unittest.main()
index c54b3e179c6ffb646781aaf6c2523d8f96567c77..00aee7a506bd9445518a57ffd18a62e87189efb9 100644 (file)
@@ -39,11 +39,11 @@ Modulus:
     1b:a4:85:ab:b0:87:7b:78:2f
 Exponent: 65537 (0x10001)
 """
-       ec1priv="""-----BEGIN EC PRIVATE KEY-----
-MHQCAQEEICpxup3qmbwffBBLrsZx7H7/i/+Wm7jTRttMM1KkaZ3DoAcGBSuBBAAK
-oUQDQgAEVil1nlGelogimdpB8fO45icsdBt2QdYkAvhqdgCWLMG0D4Rj4oCqJcyG
-2WH8J5+0DnGujfEA4TwJ90ECvLa2SA==
------END EC PRIVATE KEY-----
+       ec1priv="""-----BEGIN PRIVATE KEY-----
+MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgKnG6neqZvB98EEuuxnHs
+fv+L/5abuNNG20wzUqRpncOhRANCAARWKXWeUZ6WiCKZ2kHx87jmJyx0G3ZB1iQC
++Gp2AJYswbQPhGPigKolzIbZYfwnn7QOca6N8QDhPAn3QQK8trZI
+-----END PRIVATE KEY-----
 """
        ec1keytext="""Public-Key: (256 bit)
 pub: 
@@ -59,10 +59,15 @@ MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEVil1nlGelogimdpB8fO45icsdBt2QdYk
 AvhqdgCWLMG0D4Rj4oCqJcyG2WH8J5+0DnGujfEA4TwJ90ECvLa2SA==
 -----END PUBLIC KEY-----
 """
+       
        def test_unencrypted_pem(self):
                key=PKey(privkey=self.rsa)
                self.assertIsNotNone(key.key)
                self.assertEqual(str(key),self.rsakeytext)
+       def test_export_priv_pem(self):
+               key=PKey(privkey=self.ec1priv)
+               out=key.exportpriv()
+               self.assertEqual(self.ec1priv,out)
        def test_unencrypted_pem_ec(self):
                
                key=PKey(privkey=self.ec1priv)