]> www.wagner.pp.ru Git - oss/ctypescrypto.git/blob - ctypescrypto/pkey.py
Python 3 support for modules pbkdf2 pkey ec x509
[oss/ctypescrypto.git] / 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, )