blob: de7107372dd77c00168fd84d3e78e8161e8dacf0 [file] [log] [blame]
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001from time import time
2
Jean-Paul Calderone084b7522013-02-09 09:53:45 -08003from OpenSSL.xcrypto import *
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08004
5from tls.c import api as _api
6
7FILETYPE_PEM = _api.SSL_FILETYPE_PEM
8FILETYPE_ASN1 = _api.SSL_FILETYPE_ASN1
9
10# TODO This was an API mistake. OpenSSL has no such constant.
11FILETYPE_TEXT = 2 ** 16 - 1
12
Jean-Paul Calderone3e29ccf2013-02-19 11:32:46 -080013TYPE_RSA = _api.EVP_PKEY_RSA
14TYPE_DSA = _api.EVP_PKEY_DSA
15
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -080016
17def _bio_to_string(bio):
18 """
19 Copy the contents of an OpenSSL BIO object into a Python byte string.
20 """
21 result_buffer = _api.new('char**')
22 buffer_length = _api.BIO_get_mem_data(bio, result_buffer)
23 return _api.buffer(result_buffer[0], buffer_length)[:]
24
25
26
27def _raise_current_error():
28 errors = []
29 while True:
30 error = _api.ERR_get_error()
31 if error == 0:
32 break
33 errors.append((
34 _api.string(_api.ERR_lib_error_string(error)),
35 _api.string(_api.ERR_func_error_string(error)),
36 _api.string(_api.ERR_reason_error_string(error))))
37
38 raise Error(errors)
39
40_exception_from_error_queue = _raise_current_error
41
42
43class Error(Exception):
44 pass
45
46
47
48class PKey(object):
Jean-Paul Calderoneedafced2013-02-19 11:48:38 -080049 _only_public = False
Jean-Paul Calderone09e3bdc2013-02-19 12:15:28 -080050 _initialized = True
Jean-Paul Calderoneedafced2013-02-19 11:48:38 -080051
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -080052 def __init__(self):
Jean-Paul Calderone3e29ccf2013-02-19 11:32:46 -080053 self._pkey = _api.EVP_PKEY_new()
Jean-Paul Calderone09e3bdc2013-02-19 12:15:28 -080054 self._initialized = False
Jean-Paul Calderone3e29ccf2013-02-19 11:32:46 -080055
56
57 def generate_key(self, type, bits):
58 """
59 Generate a key of a given type, with a given number of a bits
60
61 :param type: The key type (TYPE_RSA or TYPE_DSA)
62 :param bits: The number of bits
63
64 :return: None
65 """
66 if not isinstance(type, int):
67 raise TypeError("type must be an integer")
68
69 if not isinstance(bits, int):
70 raise TypeError("bits must be an integer")
71
72 exponent = _api.new("BIGNUM**")
73 # TODO Check error return
74 # TODO Free the exponent[0]
75 _api.BN_hex2bn(exponent, "10001")
76
77 if type == TYPE_RSA:
78 if bits <= 0:
79 raise ValueError("Invalid number of bits")
80
81 rsa = _api.RSA_new();
82
83 # TODO Release GIL?
84 result = _api.RSA_generate_key_ex(rsa, bits, exponent[0], _api.NULL)
85 if result == -1:
86 1/0
87
88 result = _api.EVP_PKEY_assign_RSA(self._pkey, rsa)
89 if not result:
90 1/0
91
92 elif type == TYPE_DSA:
Jean-Paul Calderonec86fcaf2013-02-20 12:38:33 -080093 dsa = _api.DSA_generate_parameters(
94 bits, _api.NULL, 0, _api.NULL, _api.NULL, _api.NULL, _api.NULL)
95 if dsa == _api.NULL:
96 1/0
97 if not _api.DSA_generate_key(dsa):
98 1/0
99 if not _api.EVP_PKEY_assign_DSA(self._pkey, dsa):
100 1/0
Jean-Paul Calderone3e29ccf2013-02-19 11:32:46 -0800101 else:
102 raise Error("No such key type")
103
Jean-Paul Calderone09e3bdc2013-02-19 12:15:28 -0800104 self._initialized = True
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800105
106
107 def check(self):
108 """
109 Check the consistency of an RSA private key.
110
111 :return: True if key is consistent.
112 :raise Error: if the key is inconsistent.
113 :raise TypeError: if the key is of a type which cannot be checked.
114 Only RSA keys can currently be checked.
115 """
Jean-Paul Calderonec86fcaf2013-02-20 12:38:33 -0800116 if self._only_public:
117 raise TypeError("public key only")
118
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800119 if _api.EVP_PKEY_type(self._pkey.type) != _api.EVP_PKEY_RSA:
120 raise TypeError("key type unsupported")
121
122 rsa = _api.EVP_PKEY_get1_RSA(self._pkey)
123 result = _api.RSA_check_key(rsa)
124 if result:
125 return True
126 _raise_current_error()
127
128
Jean-Paul Calderonec86fcaf2013-02-20 12:38:33 -0800129 def type(self):
130 """
131 Returns the type of the key
132
133 :return: The type of the key.
134 """
135 return self._pkey.type
136
137
138 def bits(self):
139 """
140 Returns the number of bits of the key
141
142 :return: The number of bits of the key.
143 """
144 return _api.EVP_PKEY_bits(self._pkey)
145PKeyType = PKey
146
147
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800148
Jean-Paul Calderonea9de1952013-02-19 16:58:42 -0800149class X509Name(object):
150 def __init__(self, name):
151 """
152 Create a new X509Name, copying the given X509Name instance.
153
154 :param name: An X509Name object to copy
155 """
156 self._name = _api.X509_NAME_dup(name._name)
157
158
159 def __setattr__(self, name, value):
160 if name.startswith('_'):
161 return super(X509Name, self).__setattr__(name, value)
162
163 if type(name) is not str:
164 raise TypeError("attribute name must be string, not '%.200s'" % (
165 type(value).__name__,))
166
167 nid = _api.OBJ_txt2nid(name)
168 if nid == _api.NID_undef:
169 try:
170 _raise_current_error()
171 except Error:
172 pass
173 raise AttributeError("No such attribute")
174
175 # If there's an old entry for this NID, remove it
176 for i in range(_api.X509_NAME_entry_count(self._name)):
177 ent = _api.X509_NAME_get_entry(self._name, i)
178 ent_obj = _api.X509_NAME_ENTRY_get_object(ent)
179 ent_nid = _api.OBJ_obj2nid(ent_obj)
180 if nid == ent_nid:
181 ent = _api.X509_NAME_delete_entry(self._name, i)
182 _api.X509_NAME_ENTRY_free(ent)
183 break
184
185 if isinstance(value, unicode):
186 value = value.encode('utf-8')
187
188 add_result = _api.X509_NAME_add_entry_by_NID(
189 self._name, nid, _api.MBSTRING_UTF8, value, -1, -1, 0)
190 if not add_result:
191 # TODO Untested
192 1/0
193
194
195 def __getattr__(self, name):
196 """
197 Find attribute. An X509Name object has the following attributes:
198 countryName (alias C), stateOrProvince (alias ST), locality (alias L),
199 organization (alias O), organizationalUnit (alias OU), commonName (alias
200 CN) and more...
201 """
202 nid = _api.OBJ_txt2nid(name)
203 if nid == _api.NID_undef:
204 # This is a bit weird. OBJ_txt2nid indicated failure, but it seems
205 # a lower level function, a2d_ASN1_OBJECT, also feels the need to
206 # push something onto the error queue. If we don't clean that up
207 # now, someone else will bump into it later and be quite confused.
208 # See lp#314814.
209 try:
210 _raise_current_error()
211 except Error:
212 pass
213 return super(X509Name, self).__getattr__(name)
214
215 entry_index = _api.X509_NAME_get_index_by_NID(self._name, nid, -1)
216 if entry_index == -1:
217 return None
218
219 entry = _api.X509_NAME_get_entry(self._name, entry_index)
220 data = _api.X509_NAME_ENTRY_get_data(entry)
221
222 result_buffer = _api.new("unsigned char**")
223 data_length = _api.ASN1_STRING_to_UTF8(result_buffer, data)
224 if data_length < 0:
225 1/0
226
227 result = _api.buffer(result_buffer[0], data_length)[:].decode('utf-8')
228 _api.OPENSSL_free(result_buffer[0])
229 return result
230
231
232 def __cmp__(self, other):
233 if not isinstance(other, X509Name):
234 return NotImplemented
235
236 result = _api.X509_NAME_cmp(self._name, other._name)
237 # TODO result == -2 is an error case that maybe should be checked for
238 return result
239
240
241 def __repr__(self):
242 """
243 String representation of an X509Name
244 """
245 result_buffer = _api.new("char[]", 512);
246 format_result = _api.X509_NAME_oneline(
247 self._name, result_buffer, len(result_buffer))
248
249 if format_result == _api.NULL:
250 1/0
251
252 return "<X509Name object '%s'>" % (_api.string(result_buffer),)
253
254
255 def hash(self):
256 """
257 Return the hash value of this name
258
259 :return: None
260 """
261 return _api.X509_NAME_hash(self._name)
262
263
264 def der(self):
265 """
266 Return the DER encoding of this name
267
268 :return: A :py:class:`bytes` instance giving the DER encoded form of
269 this name.
270 """
271 result_buffer = _api.new('unsigned char**')
272 encode_result = _api.i2d_X509_NAME(self._name, result_buffer)
273 if encode_result < 0:
274 1/0
275
276 string_result = _api.buffer(result_buffer[0], encode_result)[:]
277 _api.OPENSSL_free(result_buffer[0])
278 return string_result
279
280
281 def get_components(self):
282 """
283 Returns the split-up components of this name.
284
285 :return: List of tuples (name, value).
286 """
287 result = []
288 for i in range(_api.X509_NAME_entry_count(self._name)):
289 ent = _api.X509_NAME_get_entry(self._name, i)
290
291 fname = _api.X509_NAME_ENTRY_get_object(ent)
292 fval = _api.X509_NAME_ENTRY_get_data(ent)
293
294 nid = _api.OBJ_obj2nid(fname)
295 name = _api.OBJ_nid2sn(nid)
296
297 result.append((
298 _api.string(name),
299 _api.string(
300 _api.ASN1_STRING_data(fval),
301 _api.ASN1_STRING_length(fval))))
302
303 return result
304X509NameType = X509Name
305
306
Jean-Paul Calderone83d22eb2013-02-20 12:19:43 -0800307class X509Extension(object):
308 def __init__(self, type_name, critical, value, subject=None, issuer=None):
309 """
310 :param typename: The name of the extension to create.
311 :type typename: :py:data:`str`
312
313 :param critical: A flag indicating whether this is a critical extension.
314
315 :param value: The value of the extension.
316 :type value: :py:data:`str`
317
318 :param subject: Optional X509 cert to use as subject.
319 :type subject: :py:class:`X509`
320
321 :param issuer: Optional X509 cert to use as issuer.
322 :type issuer: :py:class:`X509`
323
324 :return: The X509Extension object
325 """
326 ctx = _api.new("X509V3_CTX*")
Jean-Paul Calderoned418a9c2013-02-20 16:24:55 -0800327
328 # A context is necessary for any extension which uses the r2i conversion
329 # method. That is, X509V3_EXT_nconf may segfault if passed a NULL ctx.
330 # Start off by initializing most of the fields to NULL.
Jean-Paul Calderone83d22eb2013-02-20 12:19:43 -0800331 _api.X509V3_set_ctx(ctx, _api.NULL, _api.NULL, _api.NULL, _api.NULL, 0)
Jean-Paul Calderoned418a9c2013-02-20 16:24:55 -0800332
333 # We have no configuration database - but perhaps we should (some
334 # extensions may require it).
Jean-Paul Calderone83d22eb2013-02-20 12:19:43 -0800335 _api.X509V3_set_ctx_nodb(ctx)
336
Jean-Paul Calderoned418a9c2013-02-20 16:24:55 -0800337 # Initialize the subject and issuer, if appropriate. ctx is a local,
338 # and as far as I can tell none of the X509V3_* APIs invoked here steal
339 # any references, so no need to mess with reference counts or duplicates.
340 if issuer is not None:
341 if not isinstance(issuer, X509):
342 raise TypeError("issuer must be an X509 instance")
343 ctx.issuer_cert = issuer._x509
344 if subject is not None:
345 if not isinstance(subject, X509):
346 raise TypeError("subject must be an X509 instance")
347 ctx.subject_cert = subject._x509
348
Jean-Paul Calderone83d22eb2013-02-20 12:19:43 -0800349 if critical:
350 # There are other OpenSSL APIs which would let us pass in critical
351 # separately, but they're harder to use, and since value is already
352 # a pile of crappy junk smuggling a ton of utterly important
353 # structured data, what's the point of trying to avoid nasty stuff
354 # with strings? (However, X509V3_EXT_i2d in particular seems like it
355 # would be a better API to invoke. I do not know where to get the
356 # ext_struc it desires for its last parameter, though.)
357 value = "critical," + value
358
359 self._extension = _api.X509V3_EXT_nconf(
360 _api.NULL, ctx, type_name, value)
Jean-Paul Calderoned418a9c2013-02-20 16:24:55 -0800361 if self._extension == _api.NULL:
362 _raise_current_error()
363
364
365 def __str__(self):
366 """
367 :return: a nice text representation of the extension
368 """
369 bio = _api.BIO_new(_api.BIO_s_mem())
370 if bio == _api.NULL:
371 1/0
372
373 print_result = _api.X509V3_EXT_print(bio, self._extension, 0, 0)
374 if not print_result:
375 1/0
376
377 return _bio_to_string(bio)
Jean-Paul Calderone83d22eb2013-02-20 12:19:43 -0800378
379
380 def get_critical(self):
381 """
382 Returns the critical field of the X509Extension
383
384 :return: The critical field.
385 """
386 return _api.X509_EXTENSION_get_critical(self._extension)
387
388
389 def get_short_name(self):
390 """
391 Returns the short version of the type name of the X509Extension
392
393 :return: The short type name.
394 """
395 obj = _api.X509_EXTENSION_get_object(self._extension)
396 nid = _api.OBJ_obj2nid(obj)
397 return _api.string(_api.OBJ_nid2sn(nid))
398
399
Jean-Paul Calderoned418a9c2013-02-20 16:24:55 -0800400 def get_data(self):
401 """
402 Returns the data of the X509Extension
403
404 :return: A :py:data:`str` giving the X509Extension's ASN.1 encoded data.
405 """
406 octet_result = _api.X509_EXTENSION_get_data(self._extension)
407 string_result = _api.cast('ASN1_STRING*', octet_result)
408 char_result = _api.ASN1_STRING_data(string_result)
409 result_length = _api.ASN1_STRING_length(string_result)
410 return _api.buffer(char_result, result_length)[:]
411
412X509ExtensionType = X509Extension
413
Jean-Paul Calderonea9de1952013-02-19 16:58:42 -0800414
Jean-Paul Calderone066f0572013-02-20 13:43:44 -0800415class X509Req(object):
Jean-Paul Calderone4328d472013-02-20 14:28:46 -0800416 def __init__(self):
417 self._req = _api.X509_REQ_new()
418
419
420 def set_pubkey(self, pkey):
421 """
422 Set the public key of the certificate request
423
424 :param pkey: The public key to use
425 :return: None
426 """
427 set_result = _api.X509_REQ_set_pubkey(self._req, pkey._pkey)
428 if not set_result:
429 1/0
430
431
432 def get_pubkey(self):
433 """
434 Get the public key from the certificate request
435
436 :return: The public key
437 """
438 pkey = PKey.__new__(PKey)
439 pkey._pkey = _api.X509_REQ_get_pubkey(self._req)
440 if pkey._pkey == _api.NULL:
441 1/0
442 pkey._only_public = True
443 return pkey
444
445
446 def set_version(self, version):
447 """
448 Set the version subfield (RFC 2459, section 4.1.2.1) of the certificate
449 request.
450
451 :param version: The version number
452 :return: None
453 """
454 set_result = _api.X509_REQ_set_version(self._req, version)
455 if not set_result:
456 _raise_current_error()
457
458
459 def get_version(self):
460 """
461 Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate
462 request.
463
464 :return: an integer giving the value of the version subfield
465 """
466 return _api.X509_REQ_get_version(self._req)
467
468
469 def get_subject(self):
470 """
471 Create an X509Name object for the subject of the certificate request
472
473 :return: An X509Name object
474 """
475 name = X509Name.__new__(X509Name)
476 name._name = _api.X509_REQ_get_subject_name(self._req)
477 if name._name == _api.NULL:
478 1/0
479 return name
480
481
482 def add_extensions(self, extensions):
483 """
484 Add extensions to the request.
485
486 :param extensions: a sequence of X509Extension objects
487 :return: None
488 """
489 stack = _api.sk_X509_EXTENSION_new_null()
490 if stack == _api.NULL:
491 1/0
492
493 for ext in extensions:
494 if not isinstance(ext, X509Extension):
Jean-Paul Calderonec2154b72013-02-20 14:29:37 -0800495 raise ValueError("One of the elements is not an X509Extension")
Jean-Paul Calderone4328d472013-02-20 14:28:46 -0800496
497 _api.sk_X509_EXTENSION_push(stack, ext._extension)
498
499 add_result = _api.X509_REQ_add_extensions(self._req, stack)
500 if not add_result:
501 1/0
502
503
504 def sign(self, pkey, digest):
505 """
506 Sign the certificate request using the supplied key and digest
507
508 :param pkey: The key to sign with
509 :param digest: The message digest to use
510 :return: None
511 """
512 if pkey._only_public:
513 raise ValueError("Key has only public part")
514
515 if not pkey._initialized:
516 raise ValueError("Key is uninitialized")
517
518 digest_obj = _api.EVP_get_digestbyname(digest)
519 if digest_obj == _api.NULL:
520 raise ValueError("No such digest method")
521
522 sign_result = _api.X509_REQ_sign(self._req, pkey._pkey, digest_obj)
523 if not sign_result:
524 1/0
525
526
Jean-Paul Calderone066f0572013-02-20 13:43:44 -0800527X509ReqType = X509Req
528
529
530
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800531class X509(object):
532 def __init__(self):
533 # TODO Allocation failure? And why not __new__ instead of __init__?
534 self._x509 = _api.X509_new()
535
536
537 def set_version(self, version):
538 """
539 Set version number of the certificate
540
541 :param version: The version number
542 :type version: :py:class:`int`
543
544 :return: None
545 """
546 if not isinstance(version, int):
547 raise TypeError("version must be an integer")
548
549 _api.X509_set_version(self._x509, version)
550
551
552 def get_version(self):
553 """
554 Return version number of the certificate
555
556 :return: Version number as a Python integer
557 """
558 return _api.X509_get_version(self._x509)
559
560
Jean-Paul Calderoneedafced2013-02-19 11:48:38 -0800561 def get_pubkey(self):
562 """
563 Get the public key of the certificate
564
565 :return: The public key
566 """
567 pkey = PKey.__new__(PKey)
568 pkey._pkey = _api.X509_get_pubkey(self._x509)
569 if pkey._pkey == _api.NULL:
570 _raise_current_error()
571 pkey._only_public = True
572 return pkey
573
574
Jean-Paul Calderone3e29ccf2013-02-19 11:32:46 -0800575 def set_pubkey(self, pkey):
576 """
577 Set the public key of the certificate
578
579 :param pkey: The public key
580
581 :return: None
582 """
583 if not isinstance(pkey, PKey):
584 raise TypeError("pkey must be a PKey instance")
585
586 set_result = _api.X509_set_pubkey(self._x509, pkey._pkey)
587 if not set_result:
588 _raise_current_error()
589
590
591 def sign(self, pkey, digest):
592 """
593 Sign the certificate using the supplied key and digest
594
595 :param pkey: The key to sign with
596 :param digest: The message digest to use
597 :return: None
598 """
599 if not isinstance(pkey, PKey):
600 raise TypeError("pkey must be a PKey instance")
601
Jean-Paul Calderoneedafced2013-02-19 11:48:38 -0800602 if pkey._only_public:
603 raise ValueError("Key only has public part")
604
Jean-Paul Calderone09e3bdc2013-02-19 12:15:28 -0800605 if not pkey._initialized:
606 raise ValueError("Key is uninitialized")
607
Jean-Paul Calderone3e29ccf2013-02-19 11:32:46 -0800608 evp_md = _api.EVP_get_digestbyname(digest)
609 if evp_md == _api.NULL:
610 raise ValueError("No such digest method")
611
612 sign_result = _api.X509_sign(self._x509, pkey._pkey, evp_md)
613 if not sign_result:
614 _raise_current_error()
615
616
Jean-Paul Calderonee4aa3fa2013-02-19 12:12:53 -0800617 def get_signature_algorithm(self):
618 """
619 Retrieve the signature algorithm used in the certificate
620
621 :return: A byte string giving the name of the signature algorithm used in
622 the certificate.
623 :raise ValueError: If the signature algorithm is undefined.
624 """
625 alg = self._x509.cert_info.signature.algorithm
626 nid = _api.OBJ_obj2nid(alg)
627 if nid == _api.NID_undef:
628 raise ValueError("Undefined signature algorithm")
629 return _api.string(_api.OBJ_nid2ln(nid))
630
631
Jean-Paul Calderoneb4078722013-02-19 12:01:55 -0800632 def digest(self, digest_name):
633 """
634 Return the digest of the X509 object.
635
636 :param digest_name: The name of the digest algorithm to use.
637 :type digest_name: :py:class:`bytes`
638
639 :return: The digest of the object
640 """
641 digest = _api.EVP_get_digestbyname(digest_name)
642 if digest == _api.NULL:
643 raise ValueError("No such digest method")
644
645 result_buffer = _api.new("char[]", _api.EVP_MAX_MD_SIZE)
646 result_length = _api.new("unsigned int[]", 1)
647 result_length[0] = len(result_buffer)
648
649 digest_result = _api.X509_digest(
650 self._x509, digest, result_buffer, result_length)
651
652 if not digest_result:
653 1/0
654
655 return ':'.join([
656 ch.encode('hex').upper() for ch
657 in _api.buffer(result_buffer, result_length[0])])
658
659
Jean-Paul Calderone78133852013-02-19 10:41:46 -0800660 def subject_name_hash(self):
661 """
662 Return the hash of the X509 subject.
663
664 :return: The hash of the subject.
665 """
666 return _api.X509_subject_name_hash(self._x509)
667
668
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800669 def set_serial_number(self, serial):
670 """
671 Set serial number of the certificate
672
673 :param serial: The serial number
674 :type serial: :py:class:`int`
675
676 :return: None
677 """
Jean-Paul Calderone78133852013-02-19 10:41:46 -0800678 if not isinstance(serial, (int, long)):
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800679 raise TypeError("serial must be an integer")
680
Jean-Paul Calderone78133852013-02-19 10:41:46 -0800681 hex_serial = hex(serial)[2:]
682 if not isinstance(hex_serial, bytes):
683 hex_serial = hex_serial.encode('ascii')
684
685 bignum_serial = _api.new("BIGNUM**")
686
687 # BN_hex2bn stores the result in &bignum. Unless it doesn't feel like
688 # it. If bignum is still NULL after this call, then the return value is
689 # actually the result. I hope. -exarkun
690 small_serial = _api.BN_hex2bn(bignum_serial, hex_serial)
691
692 if bignum_serial[0] == _api.NULL:
693 set_result = ASN1_INTEGER_set(
694 _api.X509_get_serialNumber(self._x509), small_serial)
695 if set_result:
696 # TODO Not tested
697 _raise_current_error()
698 else:
699 asn1_serial = _api.BN_to_ASN1_INTEGER(bignum_serial[0], _api.NULL)
700 _api.BN_free(bignum_serial[0])
701 if asn1_serial == _api.NULL:
702 # TODO Not tested
703 _raise_current_error()
704 set_result = _api.X509_set_serialNumber(self._x509, asn1_serial)
705 if not set_result:
706 # TODO Not tested
707 _raise_current_error()
708
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800709
710 def get_serial_number(self):
711 """
712 Return serial number of the certificate
713
714 :return: Serial number as a Python integer
715 """
Jean-Paul Calderone78133852013-02-19 10:41:46 -0800716 asn1_serial = _api.X509_get_serialNumber(self._x509)
717 bignum_serial = _api.ASN1_INTEGER_to_BN(asn1_serial, _api.NULL)
718 try:
719 hex_serial = _api.BN_bn2hex(bignum_serial)
720 try:
721 hexstring_serial = _api.string(hex_serial)
722 serial = int(hexstring_serial, 16)
723 return serial
724 finally:
725 _api.OPENSSL_free(hex_serial)
726 finally:
727 _api.BN_free(bignum_serial)
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800728
729
730 def gmtime_adj_notAfter(self, amount):
731 """
732 Adjust the time stamp for when the certificate stops being valid
733
734 :param amount: The number of seconds by which to adjust the ending
735 validity time.
736 :type amount: :py:class:`int`
737
738 :return: None
739 """
740 if not isinstance(amount, int):
741 raise TypeError("amount must be an integer")
742
743 notAfter = _api.X509_get_notAfter(self._x509)
744 _api.X509_gmtime_adj(notAfter, amount)
745
746
Jean-Paul Calderone662afe52013-02-20 08:41:11 -0800747 def gmtime_adj_notBefore(self, amount):
748 """
749 Change the timestamp for when the certificate starts being valid to the current
750 time plus an offset.
751
752 :param amount: The number of seconds by which to adjust the starting validity
753 time.
754 :return: None
755 """
756 if not isinstance(amount, int):
757 raise TypeError("amount must be an integer")
758
759 notBefore = _api.X509_get_notBefore(self._x509)
760 _api.X509_gmtime_adj(notBefore, amount)
761
762
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800763 def has_expired(self):
764 """
765 Check whether the certificate has expired.
766
767 :return: True if the certificate has expired, false otherwise
768 """
769 now = int(time())
770 notAfter = _api.X509_get_notAfter(self._x509)
Jean-Paul Calderoned7d81272013-02-19 13:16:03 -0800771 return _api.ASN1_UTCTIME_cmp_time_t(
772 _api.cast('ASN1_UTCTIME*', notAfter), now) < 0
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800773
774
Jean-Paul Calderonec2bd4e92013-02-20 08:12:36 -0800775 def _get_boundary_time(self, which):
776 timestamp = which(self._x509)
Jean-Paul Calderoned7d81272013-02-19 13:16:03 -0800777 string_timestamp = _api.cast('ASN1_STRING*', timestamp)
778 if _api.ASN1_STRING_length(string_timestamp) == 0:
779 return None
780 elif _api.ASN1_STRING_type(string_timestamp) == _api.V_ASN1_GENERALIZEDTIME:
781 return _api.string(_api.ASN1_STRING_data(string_timestamp))
782 else:
783 generalized_timestamp = _api.new("ASN1_GENERALIZEDTIME**")
784 _api.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp)
785 if generalized_timestamp[0] == _api.NULL:
786 1/0
787 else:
Jean-Paul Calderone19247972013-02-20 08:22:15 -0800788 string_timestamp = _api.cast(
789 "ASN1_STRING*", generalized_timestamp[0])
790 string_data = _api.ASN1_STRING_data(string_timestamp)
791 string_result = _api.string(string_data)
Jean-Paul Calderoned7d81272013-02-19 13:16:03 -0800792 _api.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0])
Jean-Paul Calderone19247972013-02-20 08:22:15 -0800793 return string_result
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800794
795
Jean-Paul Calderonec2bd4e92013-02-20 08:12:36 -0800796 def get_notBefore(self):
797 """
798 Retrieve the time stamp for when the certificate starts being valid
799
800 :return: A string giving the timestamp, in the format::
801
802 YYYYMMDDhhmmssZ
803 YYYYMMDDhhmmss+hhmm
804 YYYYMMDDhhmmss-hhmm
805
806 or None if there is no value set.
807 """
808 return self._get_boundary_time(_api.X509_get_notBefore)
809
810
811 def _set_boundary_time(self, which, when):
812 if not isinstance(when, bytes):
813 raise TypeError("when must be a byte string")
814
815 boundary = which(self._x509)
816 set_result = _api.ASN1_GENERALIZEDTIME_set_string(
817 _api.cast('ASN1_GENERALIZEDTIME*', boundary), when)
818 if set_result == 0:
819 dummy = _api.ASN1_STRING_new()
820 _api.ASN1_STRING_set(dummy, when, len(when))
821 check_result = _api.ASN1_GENERALIZEDTIME_check(
822 _api.cast('ASN1_GENERALIZEDTIME*', dummy))
823 if not check_result:
824 raise ValueError("Invalid string")
825 else:
826 # TODO No tests for this case
827 raise RuntimeError("Unknown ASN1_GENERALIZEDTIME_set_string failure")
828
829
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800830 def set_notBefore(self, when):
831 """
832 Set the time stamp for when the certificate starts being valid
833
834 :param when: A string giving the timestamp, in the format:
835
836 YYYYMMDDhhmmssZ
837 YYYYMMDDhhmmss+hhmm
838 YYYYMMDDhhmmss-hhmm
839 :type when: :py:class:`bytes`
840
841 :return: None
842 """
Jean-Paul Calderonec2bd4e92013-02-20 08:12:36 -0800843 return self._set_boundary_time(_api.X509_get_notBefore, when)
Jean-Paul Calderoned7d81272013-02-19 13:16:03 -0800844
Jean-Paul Calderonec2bd4e92013-02-20 08:12:36 -0800845
846 def get_notAfter(self):
847 """
848 Retrieve the time stamp for when the certificate stops being valid
849
850 :return: A string giving the timestamp, in the format::
851
852 YYYYMMDDhhmmssZ
853 YYYYMMDDhhmmss+hhmm
854 YYYYMMDDhhmmss-hhmm
855
856 or None if there is no value set.
857 """
858 return self._get_boundary_time(_api.X509_get_notAfter)
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800859
860
861 def set_notAfter(self, when):
862 """
863 Set the time stamp for when the certificate stops being valid
864
865 :param when: A string giving the timestamp, in the format:
866
867 YYYYMMDDhhmmssZ
868 YYYYMMDDhhmmss+hhmm
869 YYYYMMDDhhmmss-hhmm
870 :type when: :py:class:`bytes`
871
872 :return: None
873 """
Jean-Paul Calderonec2bd4e92013-02-20 08:12:36 -0800874 return self._set_boundary_time(_api.X509_get_notAfter, when)
875
876
877 def _get_name(self, which):
878 name = X509Name.__new__(X509Name)
879 name._name = which(self._x509)
880 if name._name == _api.NULL:
881 1/0
882 return name
883
884
885 def _set_name(self, which, name):
Jean-Paul Calderone83d22eb2013-02-20 12:19:43 -0800886 if not isinstance(name, X509Name):
887 raise TypeError("name must be an X509Name")
Jean-Paul Calderonec2bd4e92013-02-20 08:12:36 -0800888 set_result = which(self._x509, name._name)
889 if not set_result:
890 1/0
891
892
893 def get_issuer(self):
894 """
895 Create an X509Name object for the issuer of the certificate
896
897 :return: An X509Name object
898 """
899 return self._get_name(_api.X509_get_issuer_name)
900
901
902 def set_issuer(self, issuer):
903 """
904 Set the issuer of the certificate
905
906 :param issuer: The issuer name
907 :type issuer: :py:class:`X509Name`
908
909 :return: None
910 """
911 return self._set_name(_api.X509_set_issuer_name, issuer)
Jean-Paul Calderonea9de1952013-02-19 16:58:42 -0800912
913
914 def get_subject(self):
915 """
916 Create an X509Name object for the subject of the certificate
917
918 :return: An X509Name object
919 """
Jean-Paul Calderonec2bd4e92013-02-20 08:12:36 -0800920 return self._get_name(_api.X509_get_subject_name)
Jean-Paul Calderonea9de1952013-02-19 16:58:42 -0800921
922
923 def set_subject(self, subject):
924 """
925 Set the subject of the certificate
926
927 :param subject: The subject name
928 :type subject: :py:class:`X509Name`
929 :return: None
930 """
Jean-Paul Calderonec2bd4e92013-02-20 08:12:36 -0800931 return self._set_name(_api.X509_set_subject_name, subject)
Jean-Paul Calderone83d22eb2013-02-20 12:19:43 -0800932
933
934 def get_extension_count(self):
935 """
936 Get the number of extensions on the certificate.
937
938 :return: The number of extensions as an integer.
939 """
940 return _api.X509_get_ext_count(self._x509)
941
942
943 def add_extensions(self, extensions):
944 """
945 Add extensions to the certificate.
946
947 :param extensions: a sequence of X509Extension objects
948 :return: None
949 """
950 for ext in extensions:
951 if not isinstance(ext, X509Extension):
952 raise ValueError("One of the elements is not an X509Extension")
953
954 add_result = _api.X509_add_ext(self._x509, ext._extension, -1)
955 if not add_result:
956 _raise_current_error()
957
958
959 def get_extension(self, index):
960 """
961 Get a specific extension of the certificate by index.
962
963 :param index: The index of the extension to retrieve.
964 :return: The X509Extension object at the specified index.
965 """
966 ext = X509Extension.__new__(X509Extension)
967 ext._extension = _api.X509_get_ext(self._x509, index)
968 if ext._extension == _api.NULL:
969 raise IndexError("extension index out of bounds")
970
971 ext._extension = _api.X509_EXTENSION_dup(ext._extension)
972 return ext
973
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800974X509Type = X509
975
976
977
978def load_certificate(type, buffer):
979 """
980 Load a certificate from a buffer
981
982 :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
983
984 :param buffer: The buffer the certificate is stored in
985 :type buffer: :py:class:`bytes`
986
987 :return: The X509 object
988 """
989 bio = _api.BIO_new_mem_buf(buffer, len(buffer))
Jean-Paul Calderone066f0572013-02-20 13:43:44 -0800990 if bio == _api.NULL:
991 1/0
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800992
993 try:
994 if type == FILETYPE_PEM:
995 x509 = _api.PEM_read_bio_X509(bio, _api.NULL, _api.NULL, _api.NULL)
996 elif type == FILETYPE_ASN1:
997 x509 = _api.d2i_X509_bio(bio, _api.NULL);
998 else:
999 raise ValueError(
1000 "type argument must be FILETYPE_PEM or FILETYPE_ASN1")
1001 finally:
1002 _api.BIO_free(bio)
1003
1004 if x509 == _api.NULL:
1005 _raise_current_error()
1006
1007 cert = X509.__new__(X509)
1008 cert._x509 = x509
1009 return cert
1010
1011
1012def dump_certificate(type, cert):
1013 """
1014 Dump a certificate to a buffer
1015
1016 :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
1017 :param cert: The certificate to dump
1018 :return: The buffer with the dumped certificate in
1019 """
1020 bio = _api.BIO_new(_api.BIO_s_mem())
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001021 if bio == _api.NULL:
1022 1/0
1023
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001024 if type == FILETYPE_PEM:
1025 result_code = _api.PEM_write_bio_X509(bio, cert._x509)
1026 elif type == FILETYPE_ASN1:
1027 result_code = _api.i2d_X509_bio(bio, cert._x509)
1028 elif type == FILETYPE_TEXT:
1029 result_code = _api.X509_print_ex(bio, cert._x509, 0, 0)
1030 else:
1031 raise ValueError(
1032 "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or "
1033 "FILETYPE_TEXT")
1034
1035 return _bio_to_string(bio)
1036
1037
1038
1039def dump_privatekey(type, pkey, cipher=None, passphrase=None):
1040 """
1041 Dump a private key to a buffer
1042
1043 :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
1044 :param pkey: The PKey to dump
1045 :param cipher: (optional) if encrypted PEM format, the cipher to
1046 use
1047 :param passphrase: (optional) if encrypted PEM format, this can be either
1048 the passphrase to use, or a callback for providing the
1049 passphrase.
1050 :return: The buffer with the dumped key in
1051 :rtype: :py:data:`str`
1052 """
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001053 bio = _api.BIO_new(_api.BIO_s_mem())
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001054 if bio == _api.NULL:
1055 1/0
1056
1057 if cipher is not None:
1058 cipher_obj = _api.EVP_get_cipherbyname(cipher)
1059 if cipher_obj == _api.NULL:
1060 raise ValueError("Invalid cipher name")
1061 else:
1062 cipher_obj = _api.NULL
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001063
Jean-Paul Calderone23478b32013-02-20 13:31:38 -08001064 helper = _PassphraseHelper(type, passphrase)
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001065 if type == FILETYPE_PEM:
1066 result_code = _api.PEM_write_bio_PrivateKey(
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001067 bio, pkey._pkey, cipher_obj, _api.NULL, 0,
Jean-Paul Calderone23478b32013-02-20 13:31:38 -08001068 helper.callback, helper.callback_args)
1069 helper.raise_if_problem()
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001070 elif type == FILETYPE_ASN1:
1071 result_code = _api.i2d_PrivateKey_bio(bio, pkey._pkey)
1072 elif type == FILETYPE_TEXT:
1073 rsa = _api.EVP_PKEY_get1_RSA(pkey._pkey)
1074 result_code = _api.RSA_print(bio, rsa, 0)
1075 # TODO RSA_free(rsa)?
1076 else:
1077 raise ValueError(
1078 "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or "
1079 "FILETYPE_TEXT")
1080
1081 if result_code == 0:
1082 _raise_current_error()
1083
1084 return _bio_to_string(bio)
1085
1086
1087
Jean-Paul Calderonee41f05c2013-02-20 13:28:16 -08001088class _PassphraseHelper(object):
Jean-Paul Calderone23478b32013-02-20 13:31:38 -08001089 def __init__(self, type, passphrase):
1090 if type != FILETYPE_PEM and passphrase is not None:
1091 raise ValueError("only FILETYPE_PEM key format supports encryption")
Jean-Paul Calderonee41f05c2013-02-20 13:28:16 -08001092 self._passphrase = passphrase
1093 self._problems = []
1094
1095
1096 @property
1097 def callback(self):
1098 if self._passphrase is None:
1099 return _api.NULL
1100 elif isinstance(self._passphrase, bytes):
1101 return _api.NULL
1102 elif callable(self._passphrase):
1103 return _api.callback("pem_password_cb", self._read_passphrase)
1104 else:
1105 raise TypeError("Last argument must be string or callable")
1106
1107
1108 @property
1109 def callback_args(self):
1110 if self._passphrase is None:
1111 return _api.NULL
1112 elif isinstance(self._passphrase, bytes):
1113 return self._passphrase
1114 elif callable(self._passphrase):
1115 return _api.NULL
1116 else:
1117 raise TypeError("Last argument must be string or callable")
1118
1119
1120 def raise_if_problem(self):
1121 if self._problems:
1122 try:
1123 _raise_current_error()
1124 except Error:
1125 pass
1126 raise self._problems[0]
1127
1128
1129 def _read_passphrase(self, buf, size, rwflag, userdata):
1130 try:
1131 result = self._passphrase(rwflag)
1132 if not isinstance(result, bytes):
1133 raise ValueError("String expected")
1134 if len(result) > size:
1135 raise ValueError("passphrase returned by callback is too long")
1136 for i in range(len(result)):
1137 buf[i] = result[i]
1138 return len(result)
1139 except Exception as e:
1140 self._problems.append(e)
1141 return 0
1142
1143
1144
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001145def load_privatekey(type, buffer, passphrase=None):
1146 """
1147 Load a private key from a buffer
1148
1149 :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
1150 :param buffer: The buffer the key is stored in
1151 :param passphrase: (optional) if encrypted PEM format, this can be
1152 either the passphrase to use, or a callback for
1153 providing the passphrase.
1154
1155 :return: The PKey object
1156 """
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001157 bio = _api.BIO_new_mem_buf(buffer, len(buffer))
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001158 if bio == _api.NULL:
1159 1/0
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001160
Jean-Paul Calderone23478b32013-02-20 13:31:38 -08001161 helper = _PassphraseHelper(type, passphrase)
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001162 if type == FILETYPE_PEM:
Jean-Paul Calderonee41f05c2013-02-20 13:28:16 -08001163 evp_pkey = _api.PEM_read_bio_PrivateKey(
1164 bio, _api.NULL, helper.callback, helper.callback_args)
1165 helper.raise_if_problem()
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001166 elif type == FILETYPE_ASN1:
1167 evp_pkey = _api.d2i_PrivateKey_bio(bio, _api.NULL)
1168 else:
1169 raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1")
1170
Jean-Paul Calderone31393aa2013-02-20 13:22:21 -08001171 if evp_pkey == _api.NULL:
1172 _raise_current_error()
1173
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001174 pkey = PKey.__new__(PKey)
1175 pkey._pkey = evp_pkey
1176 return pkey
1177
1178
1179
1180def dump_certificate_request(type, req):
1181 """
1182 Dump a certificate request to a buffer
1183
1184 :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
1185 :param req: The certificate request to dump
1186 :return: The buffer with the dumped certificate request in
1187 """
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001188 bio = _api.BIO_new(_api.BIO_s_mem())
1189 if bio == _api.NULL:
1190 1/0
1191
1192 if type == FILETYPE_PEM:
1193 result_code = _api.PEM_write_bio_X509_REQ(bio, req._req)
1194 elif type == FILETYPE_ASN1:
Jean-Paul Calderonec9a395f2013-02-20 16:59:21 -08001195 result_code = _api.i2d_X509_REQ_bio(bio, req._req)
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001196 elif type == FILETYPE_TEXT:
Jean-Paul Calderonec9a395f2013-02-20 16:59:21 -08001197 result_code = _api.X509_REQ_print_ex(bio, req._req, 0, 0)
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001198 else:
Jean-Paul Calderonec9a395f2013-02-20 16:59:21 -08001199 raise ValueError("type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT")
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001200
1201 if result_code == 0:
1202 1/0
1203
1204 return _bio_to_string(bio)
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -08001205
1206
1207
1208def load_certificate_request(type, buffer):
1209 """
1210 Load a certificate request from a buffer
1211
1212 :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
1213 :param buffer: The buffer the certificate request is stored in
1214 :return: The X509Req object
1215 """
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001216 bio = _api.BIO_new_mem_buf(buffer, len(buffer))
1217 if bio == _api.NULL:
1218 1/0
1219
1220 if type == FILETYPE_PEM:
1221 req = _api.PEM_read_bio_X509_REQ(bio, _api.NULL, _api.NULL, _api.NULL)
1222 elif type == FILETYPE_ASN1:
Jean-Paul Calderonec9a395f2013-02-20 16:59:21 -08001223 req = _api.d2i_X509_REQ_bio(bio, _api.NULL)
Jean-Paul Calderone066f0572013-02-20 13:43:44 -08001224 else:
1225 1/0
1226
1227 if req == _api.NULL:
1228 1/0
1229
1230 x509req = X509Req.__new__(X509Req)
1231 x509req._req = req
1232 return x509req
Jean-Paul Calderone8cf4f802013-02-20 16:45:02 -08001233
1234
1235
1236def sign(pkey, data, digest):
1237 """
1238 Sign data with a digest
1239
1240 :param pkey: Pkey to sign with
1241 :param data: data to be signed
1242 :param digest: message digest to use
1243 :return: signature
1244 """
1245 digest_obj = _api.EVP_get_digestbyname(digest)
1246 if digest_obj == _api.NULL:
1247 raise ValueError("No such digest method")
1248
1249 md_ctx = _api.new("EVP_MD_CTX*")
1250
1251 _api.EVP_SignInit(md_ctx, digest_obj)
1252 _api.EVP_SignUpdate(md_ctx, data, len(data))
1253
1254 signature_buffer = _api.new("unsigned char[]", 512)
1255 signature_length = _api.new("unsigned int*")
1256 signature_length[0] = len(signature_buffer)
1257 final_result = _api.EVP_SignFinal(
1258 md_ctx, signature_buffer, signature_length, pkey._pkey)
1259
1260 if final_result != 1:
1261 1/0
1262
1263 return _api.buffer(signature_buffer, signature_length[0])[:]
1264
1265
1266
1267def verify(cert, signature, data, digest):
1268 """
1269 Verify a signature
1270
1271 :param cert: signing certificate (X509 object)
1272 :param signature: signature returned by sign function
1273 :param data: data to be verified
1274 :param digest: message digest to use
1275 :return: None if the signature is correct, raise exception otherwise
1276 """
1277 digest_obj = _api.EVP_get_digestbyname(digest)
1278 if digest_obj == _api.NULL:
1279 raise ValueError("No such digest method")
1280
1281 pkey = _api.X509_get_pubkey(cert._x509)
1282 if pkey == _api.NULL:
1283 1/0
1284
1285 md_ctx = _api.new("EVP_MD_CTX*")
1286
1287 _api.EVP_VerifyInit(md_ctx, digest_obj)
1288 _api.EVP_VerifyUpdate(md_ctx, data, len(data))
1289 verify_result = _api.EVP_VerifyFinal(md_ctx, signature, len(signature), pkey)
1290
1291 if verify_result != 1:
1292 _raise_current_error()