]> www.wagner.pp.ru Git - oss/ctypescrypto.git/blob - ctypescrypto/pkey.py
af31a6725bc5ed04726ee7c401bed94e3ee331e6
[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
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          def __cb(buf, length, rwflag, userdata):
36              pwd = c(rwflag)
37              cnt = min(len(pwd),length)
38              memmove(buf,pwd, cnt)
39              return cnt
40     else:
41         def __cb(buf,length,rwflag,userdata):
42             cnt=min(len(c),length)
43             memmove(buf,c,cnt)
44             return cnt
45     return PW_CALLBACK_FUNC(__cb)        
46
47
48 class PKey(object):
49     """
50     Represents public/private key pair. Wrapper around EVP_PKEY
51     libcrypto object.
52
53     May contain either both private and public key (such objects can be
54     used for signing, deriving shared key as well as verifying or public
55     key only, which can be used for verifying or as peer key when
56     deriving.
57
58     @var cansign is true key has private part.
59     @var key contain pointer to EVP_PKEY and should be passed to various
60          libcrypto routines
61     """
62     def __init__(self, ptr=None, privkey=None, pubkey=None, format="PEM",
63                  cansign=False, password=None):
64         """
65         PKey object can be created from either private/public key blob or
66         from C language pointer, returned by some OpenSSL function
67
68         Following named arguments are recognized by constructor
69
70         privkey - private key blob. If this is specified, format and
71              password can be also specified
72
73         pubkey - public key blob. If this is specified, format can be
74                 specified.
75
76         ptr - pointer, returned by openssl function. If it is specified,    
77             cansign should be also specified.
78
79         These three arguments are mutually exclusive.
80
81         format - can be either 'PEM' or 'DER'. Specifies format of blob.
82         
83         password - can be string with password for encrypted key, or
84             callable  with one boolean argument, which returns password.
85             During constructor call this argument would be false.
86
87         If key is in PEM format, its encrypted status and format is
88         autodetected. If key is in DER format, than if password is
89         specified, key is assumed to be encrypted PKCS8 key otherwise
90         it is assumed to be unencrypted.
91         """
92
93         if not ptr is None:
94             self.key = ptr
95             self.cansign = cansign
96             if not privkey is None or not pubkey is None:
97                 raise TypeError("Just one of ptr, pubkey or privkey can " +
98                                 "be specified")
99         elif not privkey is None:
100             if not pubkey is None:
101                 raise TypeError("Just one of ptr, pubkey or privkey can " +
102                                 "be specified")
103             bio = Membio(privkey)
104             self.cansign = True
105             if format == "PEM":
106                 self.key = libcrypto.PEM_read_bio_PrivateKey(bio.bio, None,
107                                               _password_callback(password),
108                                                None)
109             else:
110                 if password is not None:
111                     self.key = libcrypto.d2i_PKCS8PrivateKey_bio(bio.bio,None,
112                                            _password_callback(password),
113                                            None)
114                 else:
115                     self.key = libcrypto.d2i_PrivateKey_bio(bio.bio, None)
116             if self.key is None:
117                 raise PKeyError("error parsing private key")
118         elif not pubkey is None:
119             bio = Membio(pubkey)
120             self.cansign = False
121             if format == "PEM":
122                 self.key = libcrypto.PEM_read_bio_PUBKEY(bio.bio, None,
123                                                          _password_callback(password),
124                                                          None)
125             else:
126                 self.key = libcrypto.d2i_PUBKEY_bio(bio.bio, None)
127             if self.key is None:
128                 raise PKeyError("error parsing public key")
129         else:
130             raise TypeError("Neither public, nor private key is specified")
131
132
133     def __del__(self):
134         """ Frees EVP_PKEY object (note, it is reference counted) """
135         libcrypto.EVP_PKEY_free(self.key)
136
137     def __eq__(self, other):
138         """ Compares two public keys. If one has private key and other
139             doesn't it doesn't affect result of comparation
140         """
141         return libcrypto.EVP_PKEY_cmp(self.key, other.key) == 1
142
143     def __ne__(self, other):
144         """ Compares two public key for not-equality """
145         return not self.__eq__(other)
146
147     def __str__(self):
148         """ printable representation of public key """
149         bio = Membio()
150         libcrypto.EVP_PKEY_print_public(bio.bio, self.key, 0, None)
151         return str(bio)
152
153     def sign(self, digest, **kwargs):
154         """
155         Signs given digest and retirns signature
156         Keyword arguments allows to set various algorithm-specific
157         parameters. See pkeyutl(1) manual.
158         """
159         ctx = libcrypto.EVP_PKEY_CTX_new(self.key, None)
160         if ctx is None:
161             raise PKeyError("Initailizing sign context")
162         if libcrypto.EVP_PKEY_sign_init(ctx) < 1:
163             raise PKeyError("sign_init")
164         self._configure_context(ctx, kwargs)
165         # Find out signature size
166         siglen = c_long(0)
167         if libcrypto.EVP_PKEY_sign(ctx, None, byref(siglen), digest,
168                                    len(digest)) < 1:
169             raise PKeyError("computing signature length")
170         sig = create_string_buffer(siglen.value)
171         if libcrypto.EVP_PKEY_sign(ctx, sig, byref(siglen), digest,
172                                    len(digest)) < 1:
173             raise PKeyError("signing")
174         libcrypto.EVP_PKEY_CTX_free(ctx)
175         return sig.raw[:int(siglen.value)]
176
177     def verify(self, digest, signature, **kwargs):
178         """
179         Verifies given signature on given digest
180         Returns True if Ok, False if don't match
181         Keyword arguments allows to set algorithm-specific
182         parameters
183         """
184         ctx = libcrypto.EVP_PKEY_CTX_new(self.key, None)
185         if ctx is None:
186             raise PKeyError("Initailizing verify context")
187         if libcrypto.EVP_PKEY_verify_init(ctx) < 1:
188             raise PKeyError("verify_init")
189         self._configure_context(ctx, kwargs)
190         ret = libcrypto.EVP_PKEY_verify(ctx, signature, len(signature), digest,
191                                         len(digest))
192         if ret < 0:
193             raise PKeyError("Signature verification")
194         libcrypto.EVP_PKEY_CTX_free(ctx)
195         return ret > 0
196
197     def derive(self, peerkey, **kwargs):
198         """
199         Derives shared key (DH,ECDH,VKO 34.10). Requires
200         private key available
201
202         @param peerkey - other key (may be public only)
203
204         Keyword parameters are algorithm-specific
205         """
206         if not self.cansign:
207             raise ValueError("No private key available")
208         ctx = libcrypto.EVP_PKEY_CTX_new(self.key, None)
209         if ctx is None:
210             raise PKeyError("Initailizing derive context")
211         if libcrypto.EVP_PKEY_derive_init(ctx) < 1:
212             raise PKeyError("derive_init")
213
214         # This is workaround around missing functionality in GOST engine
215         # it provides only numeric control command to set UKM, not
216         # string one.
217         self._configure_context(ctx, kwargs, ["ukm"])
218         if libcrypto.EVP_PKEY_derive_set_peer(ctx, peerkey.key) <= 0:
219             raise PKeyError("Cannot set peer key")
220         if "ukm" in kwargs:
221             # We just hardcode numeric command to set UKM here
222             if libcrypto.EVP_PKEY_CTX_ctrl(ctx, -1, 1 << 10, 8, 8,
223                                            kwargs["ukm"]) <= 0:
224                 raise PKeyError("Cannot set UKM")
225         keylen = c_long(0)
226         if libcrypto.EVP_PKEY_derive(ctx, None, byref(keylen)) <= 0:
227             raise PKeyError("computing shared key length")
228         buf = create_string_buffer(keylen.value)
229         if libcrypto.EVP_PKEY_derive(ctx, buf, byref(keylen)) <= 0:
230             raise PKeyError("computing actual shared key")
231         libcrypto.EVP_PKEY_CTX_free(ctx)
232         return buf.raw[:int(keylen.value)]
233
234     @staticmethod
235     def generate(algorithm, **kwargs):
236         """
237         Generates new private-public key pair for given algorithm
238         (string like 'rsa','ec','gost2001') and algorithm-specific
239         parameters.
240
241         Algorithm specific paramteers for RSA:
242
243         rsa_keygen_bits=number - size of key to be generated
244         rsa_keygen_pubexp - RSA public expontent(default 65537)
245
246         Algorithm specific parameters for DSA,DH and EC
247
248         paramsfrom=PKey object
249
250         copy parameters of newly generated key from existing key
251
252         Algorithm specific parameters for GOST2001
253
254         paramset= paramset name where name is one of
255         'A','B','C','XA','XB','test'
256
257         paramsfrom does work too
258         """
259         tmpeng = c_void_p(None)
260         ameth = libcrypto.EVP_PKEY_asn1_find_str(byref(tmpeng), algorithm, -1)
261         if ameth is None:
262             raise PKeyError("Algorithm %s not foind\n"%(algorithm))
263         clear_err_stack()
264         pkey_id = c_int(0)
265         libcrypto.EVP_PKEY_asn1_get0_info(byref(pkey_id), None, None, None,
266                                           None, ameth)
267         #libcrypto.ENGINE_finish(tmpeng)
268         if "paramsfrom" in kwargs:
269             ctx = libcrypto.EVP_PKEY_CTX_new(kwargs["paramsfrom"].key, None)
270         else:
271             ctx = libcrypto.EVP_PKEY_CTX_new_id(pkey_id, None)
272         # FIXME support EC curve as keyword param by invoking paramgen
273         # operation
274         if ctx is None:
275             raise PKeyError("Creating context for key type %d"%(pkey_id.value))
276         if libcrypto.EVP_PKEY_keygen_init(ctx) <= 0:
277             raise PKeyError("keygen_init")
278         PKey._configure_context(ctx, kwargs, ["paramsfrom"])
279         key = c_void_p(None)
280         if libcrypto.EVP_PKEY_keygen(ctx, byref(key)) <= 0:
281             raise PKeyError("Error generating key")
282         libcrypto.EVP_PKEY_CTX_free(ctx)
283         return PKey(ptr=key, cansign=True)
284
285     def exportpub(self, format="PEM"):
286         """
287         Returns public key as PEM or DER structure.
288         """
289         bio = Membio()
290         if format == "PEM":
291             retcode = libcrypto.PEM_write_bio_PUBKEY(bio.bio, self.key)
292         else:
293             retcode = libcrypto.i2d_PUBKEY_bio(bio.bio, self.key)
294         if retcode == 0:
295             raise PKeyError("error serializing public key")
296         return str(bio)
297
298     def exportpriv(self, format="PEM", password=None, cipher=None):
299         """
300         Returns private key as PEM or DER Structure.
301         If password and cipher are specified, encrypts key
302         on given password, using given algorithm. Cipher must be
303         an ctypescrypto.cipher.CipherType object
304
305         Password can be either string or function with one argument,
306         which returns password. It is called with argument True, which
307         means, that we are encrypting key, and password should be
308         verified (requested twice from user, for example).
309         """
310         bio = Membio()
311         if cipher is None:
312             evp_cipher = None
313         else:
314             evp_cipher = cipher.cipher
315         if format == "PEM":
316             ret = libcrypto.PEM_write_bio_PrivateKey(bio.bio, self.key,
317                                          evp_cipher, None, 0,
318                                          _password_callback(password),
319                                          None)
320         else:
321             ret = libcrypto.i2d_PKCS8PrivateKey_bio(bio.bio, self.key,
322                                                evp_cipher, None, 0,
323                                               _password_callback(password),
324                                                None)
325         if ret == 0:
326             raise PKeyError("error serializing private key")
327         return str(bio)
328
329     @staticmethod
330     def _configure_context(ctx, opts, skip=()):
331         """
332         Configures context of public key operations
333         @param ctx - context to configure
334         @param opts - dictionary of options (from kwargs of calling
335             function)
336         @param skip - list of options which shouldn't be passed to
337             context
338         """
339
340         for oper in opts:
341             if oper in skip:
342                 continue
343             ret = libcrypto.EVP_PKEY_CTX_ctrl_str(ctx, oper, str(opts[oper]))
344             if ret == -2:
345                 raise PKeyError("Parameter %s is not supported by key" % oper)
346             if ret < 1:
347                 raise PKeyError("Error setting parameter %s" % oper)
348 # Declare function prototypes
349 libcrypto.EVP_PKEY_cmp.argtypes = (c_void_p, c_void_p)
350 libcrypto.PEM_read_bio_PrivateKey.restype = c_void_p
351 libcrypto.PEM_read_bio_PrivateKey.argtypes = (c_void_p, POINTER(c_void_p),
352                                               PW_CALLBACK_FUNC, c_char_p)
353 libcrypto.PEM_read_bio_PUBKEY.restype = c_void_p
354 libcrypto.PEM_read_bio_PUBKEY.argtypes = (c_void_p, POINTER(c_void_p),
355                                           PW_CALLBACK_FUNC, c_char_p)
356 libcrypto.d2i_PUBKEY_bio.restype = c_void_p
357 libcrypto.d2i_PUBKEY_bio.argtypes = (c_void_p, c_void_p)
358 libcrypto.d2i_PrivateKey_bio.restype = c_void_p
359 libcrypto.d2i_PrivateKey_bio.argtypes = (c_void_p, c_void_p)
360 libcrypto.EVP_PKEY_print_public.argtypes = (c_void_p, c_void_p, c_int, c_void_p)
361 libcrypto.EVP_PKEY_asn1_find_str.restype = c_void_p
362 libcrypto.EVP_PKEY_asn1_find_str.argtypes = (c_void_p, c_char_p, c_int)
363 libcrypto.EVP_PKEY_asn1_get0_info.restype = c_int
364 libcrypto.EVP_PKEY_asn1_get0_info.argtypes = (POINTER(c_int), POINTER(c_int),
365                                               POINTER(c_int), POINTER(c_char_p),
366                                               POINTER(c_char_p), c_void_p)
367 libcrypto.EVP_PKEY_cmp.restype = c_int
368 libcrypto.EVP_PKEY_cmp.argtypes = (c_void_p, c_void_p)
369 libcrypto.EVP_PKEY_CTX_ctrl_str.restype = c_int
370 libcrypto.EVP_PKEY_CTX_ctrl_str.argtypes = (c_void_p, c_void_p, c_void_p)
371 libcrypto.EVP_PKEY_CTX_ctrl.restype = c_int
372 libcrypto.EVP_PKEY_CTX_ctrl.argtypes = (c_void_p, c_int, c_int, c_int, c_int,
373                                         c_void_p)
374 libcrypto.EVP_PKEY_CTX_free.argtypes = (c_void_p, )
375 libcrypto.EVP_PKEY_CTX_new.restype = c_void_p
376 libcrypto.EVP_PKEY_CTX_new.argtypes = (c_void_p, c_void_p)
377 libcrypto.EVP_PKEY_CTX_new_id.restype = c_void_p
378 libcrypto.EVP_PKEY_CTX_new_id.argtypes = (c_int, c_void_p)
379 libcrypto.EVP_PKEY_derive.restype = c_int
380 libcrypto.EVP_PKEY_derive.argtypes = (c_void_p, c_char_p, POINTER(c_long))
381 libcrypto.EVP_PKEY_derive_init.restype = c_int
382 libcrypto.EVP_PKEY_derive_init.argtypes = (c_void_p, )
383 libcrypto.EVP_PKEY_derive_set_peer.restype = c_int
384 libcrypto.EVP_PKEY_derive_set_peer.argtypes = (c_void_p, c_void_p)
385 libcrypto.EVP_PKEY_free.argtypes = (c_void_p,)
386 libcrypto.EVP_PKEY_keygen.restype = c_int
387 libcrypto.EVP_PKEY_keygen.argtypes = (c_void_p, c_void_p)
388 libcrypto.EVP_PKEY_keygen_init.restype = c_int
389 libcrypto.EVP_PKEY_keygen_init.argtypes = (c_void_p, )
390 libcrypto.EVP_PKEY_sign.restype = c_int
391 libcrypto.EVP_PKEY_sign.argtypes = (c_void_p, c_char_p, POINTER(c_long),
392                                     c_char_p, c_long)
393 libcrypto.EVP_PKEY_sign_init.restype = c_int
394 libcrypto.EVP_PKEY_sign_init.argtypes = (c_void_p, )
395 libcrypto.EVP_PKEY_verify.restype = c_int
396 libcrypto.EVP_PKEY_verify.argtypes = (c_void_p, c_char_p, c_long, c_char_p,
397                                       c_long)
398 libcrypto.EVP_PKEY_verify_init.restype = c_int
399 libcrypto.EVP_PKEY_verify_init.argtypes = (c_void_p, )
400 libcrypto.PEM_write_bio_PrivateKey.argtypes = (c_void_p, c_void_p, c_void_p,
401                                                c_char_p, c_int,
402                                                PW_CALLBACK_FUNC, c_char_p)
403 libcrypto.PEM_write_bio_PUBKEY.argtypes = (c_void_p, c_void_p)
404 libcrypto.i2d_PUBKEY_bio.argtypes = (c_void_p, c_void_p)
405 libcrypto.i2d_PKCS8PrivateKey_bio.argtypes = (c_void_p, c_void_p, c_void_p,
406                                               c_char_p, c_int,
407                                               PW_CALLBACK_FUNC, c_char_p)
408 libcrypto.d2i_PKCS8PrivateKey_bio.restype = c_void_p                                              
409 libcrypto.d2i_PKCS8PrivateKey_bio.argtypes = (c_void_p,c_void_p,
410                                               PW_CALLBACK_FUNC,c_void_p)                                            
411 libcrypto.ENGINE_finish.argtypes = (c_void_p, )