"""
-from ctypes import c_char_p, c_void_p, c_int, c_long, POINTER
+from ctypes import c_char, c_char_p, c_void_p, c_int, c_long, POINTER
from ctypes import create_string_buffer, byref, memmove, CFUNCTYPE
-from ctypescrypto import libcrypto
+from ctypescrypto import libcrypto,pyver,bintype,chartype
from ctypescrypto.exception import LibCryptoError, clear_err_stack
from ctypescrypto.bio import Membio
-__all__ = ['PKeyError', 'password_callback', 'PKey', 'PW_CALLBACK_FUNC']
+__all__ = ['PKeyError', 'PKey', 'PW_CALLBACK_FUNC']
class PKeyError(LibCryptoError):
""" Exception thrown if libcrypto finctions return an error """
pass
-PW_CALLBACK_FUNC = CFUNCTYPE(c_int, c_char_p, c_int, c_int, c_char_p)
+PW_CALLBACK_FUNC = CFUNCTYPE(c_int, POINTER(c_char), c_int, c_int, c_char_p)
""" Function type for pem password callback """
-def password_callback(buf, length, rwflag, userdata):
+def _password_callback(c):
"""
- Example password callback for private key. Assumes that
- password is stored in the userdata parameter, so allows to pass password
- from constructor arguments to the libcrypto keyloading functions
- """
- cnt = len(userdata)
- if length < cnt:
- cnt = length
- memmove(buf, userdata, cnt)
- return cnt
+ Converts given user function or string to C password callback
+ function, passable to openssl.
+
+ IF function is passed, it would be called upon reading or writing
+ PEM format private key with one argument which is True if we are
+ writing key and should verify passphrase and false if we are reading
-_cb = PW_CALLBACK_FUNC(password_callback)
+ """
+ if c is None:
+ return PW_CALLBACK_FUNC(0)
+ if callable(c):
+ if pyver ==2 :
+ def __cb(buf, length, rwflag, userdata):
+ pwd = c(rwflag)
+ cnt = min(len(pwd),length)
+ memmove(buf,pwd, cnt)
+ return cnt
+ else:
+ def __cb(buf, length, rwflag, userdata):
+ pwd = c(rwflag).encode("utf-8")
+ cnt = min(len(pwd),length)
+ memmove(buf,pwd, cnt)
+ return cnt
+ else:
+ if pyver > 2:
+ c=c.encode("utf-8")
+ def __cb(buf,length,rwflag,userdata):
+ cnt=min(len(c),length)
+ memmove(buf,c,cnt)
+ return cnt
+ return PW_CALLBACK_FUNC(__cb)
+
+def _keybio(blob, format):
+ # But DER string should be binary
+ if format == "PEM" and isinstance(blob,chartype):
+ return Membio(blob.encode("ascii"),clone=True)
+ elif isinstance(blob,bintype):
+ return Membio(blob)
+ else:
+ raise TypeError("Key should be either blob or PEM string")
class PKey(object):
"""
libcrypto routines
"""
def __init__(self, ptr=None, privkey=None, pubkey=None, format="PEM",
- cansign=False, password=None, callback=_cb):
+ cansign=False, password=None):
+ """
+ PKey object can be created from either private/public key blob or
+ from C language pointer, returned by some OpenSSL function
+
+ Following named arguments are recognized by constructor
+
+ privkey - private key blob. If this is specified, format and
+ password can be also specified
+
+ pubkey - public key blob. If this is specified, format can be
+ specified.
+
+ ptr - pointer, returned by openssl function. If it is specified,
+ cansign should be also specified.
+
+ These three arguments are mutually exclusive.
+
+ format - can be either 'PEM' or 'DER'. Specifies format of blob.
+
+ password - can be string with password for encrypted key, or
+ callable with one boolean argument, which returns password.
+ During constructor call this argument would be false.
+
+ If key is in PEM format, its encrypted status and format is
+ autodetected. If key is in DER format, than if password is
+ specified, key is assumed to be encrypted PKCS8 key otherwise
+ it is assumed to be unencrypted.
+ """
+
if not ptr is None:
self.key = ptr
self.cansign = cansign
if not pubkey is None:
raise TypeError("Just one of ptr, pubkey or privkey can " +
"be specified")
- bio = Membio(privkey)
+ bio=_keybio(privkey,format)
self.cansign = True
if format == "PEM":
self.key = libcrypto.PEM_read_bio_PrivateKey(bio.bio, None,
- callback,
- c_char_p(password))
+ _password_callback(password),
+ None)
else:
- self.key = libcrypto.d2i_PrivateKey_bio(bio.bio, None)
+ if password is not None:
+ self.key = libcrypto.d2i_PKCS8PrivateKey_bio(bio.bio,None,
+ _password_callback(password),
+ None)
+ else:
+ self.key = libcrypto.d2i_PrivateKey_bio(bio.bio, None)
if self.key is None:
raise PKeyError("error parsing private key")
elif not pubkey is None:
- bio = Membio(pubkey)
+ bio = _keybio(pubkey,format)
self.cansign = False
if format == "PEM":
self.key = libcrypto.PEM_read_bio_PUBKEY(bio.bio, None,
- callback,
- c_char_p(password))
+ _password_callback(password),
+ None)
else:
self.key = libcrypto.d2i_PUBKEY_bio(bio.bio, None)
if self.key is None:
def __del__(self):
""" Frees EVP_PKEY object (note, it is reference counted) """
- libcrypto.EVP_PKEY_free(self.key)
+ if hasattr(self,"key"):
+ libcrypto.EVP_PKEY_free(self.key)
def __eq__(self, other):
""" Compares two public keys. If one has private key and other
paramsfrom does work too
"""
tmpeng = c_void_p(None)
- ameth = libcrypto.EVP_PKEY_asn1_find_str(byref(tmpeng), algorithm, -1)
+ if isinstance(algorithm, chartype):
+ alg = algorithm.encode("ascii")
+ else:
+ alg = algorithm
+ ameth = libcrypto.EVP_PKEY_asn1_find_str(byref(tmpeng), alg, -1)
if ameth is None:
raise PKeyError("Algorithm %s not foind\n"%(algorithm))
clear_err_stack()
raise PKeyError("error serializing public key")
return str(bio)
- def exportpriv(self, format="PEM", password=None, cipher=None,
- callback=_cb):
+ def exportpriv(self, format="PEM", password=None, cipher=None):
"""
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
+
+ Password can be either string or function with one argument,
+ which returns password. It is called with argument True, which
+ means, that we are encrypting key, and password should be
+ verified (requested twice from user, for example).
"""
bio = Membio()
if cipher is None:
evp_cipher = cipher.cipher
if format == "PEM":
ret = libcrypto.PEM_write_bio_PrivateKey(bio.bio, self.key,
- evp_cipher, None, 0,
- callback,
- c_char_p(password))
+ evp_cipher, None, 0,
+ _password_callback(password),
+ None)
+ if ret ==0:
+ raise PKeyError("error serializing private key")
+ return str(bio)
else:
ret = libcrypto.i2d_PKCS8PrivateKey_bio(bio.bio, self.key,
- evp_cipher, None, 0,
- callback,
- c_char_p(password))
- if ret == 0:
- raise PKeyError("error serializing private key")
- return str(bio)
+ evp_cipher, None, 0,
+ _password_callback(password),
+ None)
+ if ret ==0:
+ raise PKeyError("error serializing private key")
+ return bintype(bio)
@staticmethod
def _configure_context(ctx, opts, skip=()):
for oper in opts:
if oper in skip:
continue
- ret = libcrypto.EVP_PKEY_CTX_ctrl_str(ctx, oper, str(opts[oper]))
+ if isinstance(oper,chartype):
+ op = oper.encode("ascii")
+ else:
+ op = oper
+ if isinstance(opts[oper],chartype):
+ value = opts[oper].encode("ascii")
+ elif isinstance(opts[oper],bintype):
+ value = opts[oper]
+ else:
+ if pyver == 2:
+ value = str(opts[oper])
+ else:
+ value = str(opts[oper]).encode('ascii')
+ ret = libcrypto.EVP_PKEY_CTX_ctrl_str(ctx, op, value)
if ret == -2:
raise PKeyError("Parameter %s is not supported by key" % oper)
if ret < 1:
libcrypto.i2d_PKCS8PrivateKey_bio.argtypes = (c_void_p, c_void_p, c_void_p,
c_char_p, c_int,
PW_CALLBACK_FUNC, c_char_p)
+libcrypto.d2i_PKCS8PrivateKey_bio.restype = c_void_p
+libcrypto.d2i_PKCS8PrivateKey_bio.argtypes = (c_void_p,c_void_p,
+ PW_CALLBACK_FUNC,c_void_p)
libcrypto.ENGINE_finish.argtypes = (c_void_p, )