From: Victor Wagner Date: Sun, 26 Oct 2014 11:43:56 +0000 (+0300) Subject: Added tests for ec key creation from raw material and for private key X-Git-Url: https://www.wagner.pp.ru/gitweb/?p=oss%2Fctypescrypto.git;a=commitdiff_plain;h=d817f7ee1103370ab5355871e744dfb5c15bf2b4 Added tests for ec key creation from raw material and for private key serialization. Added skeleton for cms module (never run) --- diff --git a/ctypescrypto/cms.py b/ctypescrypto/cms.py new file mode 100644 index 0000000..9defb1d --- /dev/null +++ b/ctypescrypto/cms.py @@ -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) diff --git a/ctypescrypto/digest.py b/ctypescrypto/digest.py index 4e33d92..df4e580 100644 --- a/ctypescrypto/digest.py +++ b/ctypescrypto/digest.py @@ -1,5 +1,6 @@ """ - Implmenets interface to OpenSSL EVP_Digest* functions. + Implements interface to OpenSSL EVP_Digest* functions. + Interface made as close to hashlib as possible. This module is really an excess effort. Hashlib allows access to diff --git a/ctypescrypto/ec.py b/ctypescrypto/ec.py index d325212..3d880cb 100644 --- a/ctypescrypto/ec.py +++ b/ctypescrypto/ec.py @@ -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) + + diff --git a/ctypescrypto/pkey.py b/ctypescrypto/pkey.py index f011443..10366ee 100644 --- a/ctypescrypto/pkey.py +++ b/ctypescrypto/pkey.py @@ -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