1 #----------------------------------------------------------------------
2 # Copyright (c) 2008 Board of Trustees, Princeton University
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and/or hardware specification (the "Work") to
6 # deal in the Work without restriction, including without limitation the
7 # rights to use, copy, modify, merge, publish, distribute, sublicense,
8 # and/or sell copies of the Work, and to permit persons to whom the Work
9 # is furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Work.
14 # THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
22 #----------------------------------------------------------------------
25 # SFA uses two crypto libraries: pyOpenSSL and M2Crypto to implement
26 # the necessary crypto functionality. Ideally just one of these libraries
27 # would be used, but unfortunately each of these libraries is independently
28 # lacking. The pyOpenSSL library is missing many necessary functions, and
29 # the M2Crypto library has crashed inside of some of the functions. The
30 # design decision is to use pyOpenSSL whenever possible as it seems more
31 # stable, and only use M2Crypto for those functions that are not possible
34 # This module exports two classes: Keypair and Certificate.
42 from tempfile import mkstemp
44 from OpenSSL import crypto
46 from M2Crypto import X509
48 from sfa.util.faults import CertExpired, CertMissingParent, CertNotSignedByParent
49 from sfa.util.sfalogging import logger
51 # this tends to generate quite some logs for little or no value
52 debug_verify_chain = False
54 glo_passphrase_callback = None
57 # A global callback may be implemented for requesting passphrases from the
58 # user. The function will be called with three arguments:
60 # keypair_obj: the keypair object that is calling the passphrase
61 # string: the string containing the private key that's being loaded
62 # x: unknown, appears to be 0, comes from pyOpenSSL and/or m2crypto
64 # The callback should return a string containing the passphrase.
66 def set_passphrase_callback(callback_func):
67 global glo_passphrase_callback
69 glo_passphrase_callback = callback_func
72 # Sets a fixed passphrase.
74 def set_passphrase(passphrase):
75 set_passphrase_callback( lambda k,s,x: passphrase )
78 # Check to see if a passphrase works for a particular private key string.
79 # Intended to be used by passphrase callbacks for input validation.
81 def test_passphrase(string, passphrase):
83 crypto.load_privatekey(crypto.FILETYPE_PEM, string, (lambda x: passphrase))
88 def convert_public_key(key):
89 keyconvert_path = "/usr/bin/keyconvert.py"
90 if not os.path.isfile(keyconvert_path):
91 raise IOError, "Could not find keyconvert in %s" % keyconvert_path
93 # we can only convert rsa keys
95 raise Exception, "keyconvert: dss keys are not supported"
97 (ssh_f, ssh_fn) = tempfile.mkstemp()
98 ssl_fn = tempfile.mktemp()
102 cmd = keyconvert_path + " " + ssh_fn + " " + ssl_fn
105 # this check leaves the temporary file containing the public key so
106 # that it can be expected to see why it failed.
107 # TODO: for production, cleanup the temporary files
108 if not os.path.exists(ssl_fn):
109 raise Exception, "keyconvert: generated certificate not found. keyconvert may have failed."
113 k.load_pubkey_from_file(ssl_fn)
116 logger.log_exc("convert_public_key caught exception")
119 # remove the temporary files
120 if os.path.exists(ssh_fn):
122 if os.path.exists(ssl_fn):
126 # Public-private key pairs are implemented by the Keypair class.
127 # A Keypair object may represent both a public and private key pair, or it
128 # may represent only a public key (this usage is consistent with OpenSSL).
131 key = None # public/private keypair
132 m2key = None # public key (m2crypto format)
135 # Creates a Keypair object
136 # @param create If create==True, creates a new public/private key and
137 # stores it in the object
138 # @param string If string!=None, load the keypair from the string (PEM)
139 # @param filename If filename!=None, load the keypair from the file
141 def __init__(self, create=False, string=None, filename=None):
145 self.load_from_string(string)
147 self.load_from_file(filename)
150 # Create a RSA public/private key pair and store it inside the keypair object
153 self.key = crypto.PKey()
154 self.key.generate_key(crypto.TYPE_RSA, 2048)
157 # Save the private key to a file
158 # @param filename name of file to store the keypair in
160 def save_to_file(self, filename):
161 open(filename, 'w').write(self.as_pem())
162 self.filename=filename
165 # Load the private key from a file. Implicity the private key includes the public key.
167 def load_from_file(self, filename):
168 self.filename=filename
169 buffer = open(filename, 'r').read()
170 self.load_from_string(buffer)
173 # Load the private key from a string. Implicitly the private key includes the public key.
175 def load_from_string(self, string):
176 if glo_passphrase_callback:
177 self.key = crypto.load_privatekey(
178 crypto.FILETYPE_PEM, string, functools.partial(glo_passphrase_callback, self, string))
179 self.m2key = M2Crypto.EVP.load_key_string(
180 string, functools.partial(glo_passphrase_callback, self, string))
182 self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string)
183 self.m2key = M2Crypto.EVP.load_key_string(string)
186 # Load the public key from a string. No private key is loaded.
188 def load_pubkey_from_file(self, filename):
189 # load the m2 public key
190 m2rsakey = M2Crypto.RSA.load_pub_key(filename)
191 self.m2key = M2Crypto.EVP.PKey()
192 self.m2key.assign_rsa(m2rsakey)
194 # create an m2 x509 cert
195 m2name = M2Crypto.X509.X509_Name()
196 m2name.add_entry_by_txt(field="CN", type=0x1001, entry="junk", len=-1, loc=-1, set=0)
197 m2x509 = M2Crypto.X509.X509()
198 m2x509.set_pubkey(self.m2key)
199 m2x509.set_serial_number(0)
200 m2x509.set_issuer_name(m2name)
201 m2x509.set_subject_name(m2name)
202 ASN1 = M2Crypto.ASN1.ASN1_UTCTIME()
204 m2x509.set_not_before(ASN1)
205 m2x509.set_not_after(ASN1)
206 # x509v3 so it can have extensions
207 # prob not necc since this cert itself is junk but still...
208 m2x509.set_version(2)
209 junk_key = Keypair(create=True)
210 m2x509.sign(pkey=junk_key.get_m2_pubkey(), md="sha1")
212 # convert the m2 x509 cert to a pyopenssl x509
213 m2pem = m2x509.as_pem()
214 pyx509 = crypto.load_certificate(crypto.FILETYPE_PEM, m2pem)
216 # get the pyopenssl pkey from the pyopenssl x509
217 self.key = pyx509.get_pubkey()
218 self.filename=filename
221 # Load the public key from a string. No private key is loaded.
223 def load_pubkey_from_string(self, string):
224 (f, fn) = tempfile.mkstemp()
227 self.load_pubkey_from_file(fn)
231 # Return the private key in PEM format.
234 return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key)
237 # Return an M2Crypto key object
239 def get_m2_pubkey(self):
241 self.m2key = M2Crypto.EVP.load_key_string(self.as_pem())
245 # Returns a string containing the public key represented by this object.
247 def get_pubkey_string(self):
248 m2pkey = self.get_m2_pubkey()
249 return base64.b64encode(m2pkey.as_der())
252 # Return an OpenSSL pkey object
254 def get_openssl_pkey(self):
258 # Given another Keypair object, return TRUE if the two keys are the same.
260 def is_same(self, pkey):
261 return self.as_pem() == pkey.as_pem()
263 def sign_string(self, data):
264 k = self.get_m2_pubkey()
267 return base64.b64encode(k.sign_final())
269 def verify_string(self, data, sig):
270 k = self.get_m2_pubkey()
272 k.verify_update(data)
273 return M2Crypto.m2.verify_final(k.ctx, base64.b64decode(sig), k.pkey)
275 def compute_hash(self, value):
276 return self.sign_string(str(value))
279 def get_filename(self):
280 return getattr(self,'filename',None)
282 def dump (self, *args, **kwargs):
283 print self.dump_string(*args, **kwargs)
285 def dump_string (self):
287 result += "KEYPAIR: pubkey=%40s..."%self.get_pubkey_string()
288 filename=self.get_filename()
289 if filename: result += "Filename %s\n"%filename
293 # The certificate class implements a general purpose X509 certificate, making
294 # use of the appropriate pyOpenSSL or M2Crypto abstractions. It also adds
295 # several addition features, such as the ability to maintain a chain of
296 # parent certificates, and storage of application-specific data.
298 # Certificates include the ability to maintain a chain of parents. Each
299 # certificate includes a pointer to it's parent certificate. When loaded
300 # from a file or a string, the parent chain will be automatically loaded.
301 # When saving a certificate to a file or a string, the caller can choose
302 # whether to save the parent certificates as well.
309 # issuerSubject = None
311 isCA = None # will be a boolean once set
313 separator="-----parent-----"
316 # Create a certificate object.
318 # @param lifeDays life of cert in days - default is 1825==5 years
319 # @param create If create==True, then also create a blank X509 certificate.
320 # @param subject If subject!=None, then create a blank certificate and set
322 # @param string If string!=None, load the certficate from the string.
323 # @param filename If filename!=None, load the certficiate from the file.
324 # @param isCA If !=None, set whether this cert is for a CA
326 def __init__(self, lifeDays=1825, create=False, subject=None, string=None, filename=None, isCA=None):
327 # these used to be defined in the class !
329 self.issuerKey = None
330 self.issuerSubject = None
334 if create or subject:
335 self.create(lifeDays)
337 self.set_subject(subject)
339 self.load_from_string(string)
341 self.load_from_file(filename)
343 # Set the CA bit if a value was supplied
347 # Create a blank X509 certificate and store it in this object.
349 def create(self, lifeDays=1825):
350 self.x509 = crypto.X509()
351 # FIXME: Use different serial #s
352 self.x509.set_serial_number(3)
353 self.x509.gmtime_adj_notBefore(0) # 0 means now
354 self.x509.gmtime_adj_notAfter(lifeDays*60*60*24) # five years is default
355 self.x509.set_version(2) # x509v3 so it can have extensions
359 # Given a pyOpenSSL X509 object, store that object inside of this
360 # certificate object.
362 def load_from_pyopenssl_x509(self, x509):
366 # Load the certificate from a string
368 def load_from_string(self, string):
369 # if it is a chain of multiple certs, then split off the first one and
370 # load it (support for the ---parent--- tag as well as normal chained certs)
372 if string is None or string.strip() == "":
373 logger.warn("Empty string in load_from_string")
376 string = string.strip()
378 # If it's not in proper PEM format, wrap it
379 if string.count('-----BEGIN CERTIFICATE') == 0:
380 string = '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----' % string
382 # If there is a PEM cert in there, but there is some other text first
383 # such as the text of the certificate, skip the text
384 beg = string.find('-----BEGIN CERTIFICATE')
386 # skipping over non cert beginning
387 string = string[beg:]
391 if string.count('-----BEGIN CERTIFICATE-----') > 1 and \
392 string.count(Certificate.separator) == 0:
393 parts = string.split('-----END CERTIFICATE-----',1)
394 parts[0] += '-----END CERTIFICATE-----'
396 parts = string.split(Certificate.separator, 1)
398 self.x509 = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0])
400 if self.x509 is None:
401 logger.warn("Loaded from string but cert is None: %s" % string)
403 # if there are more certs, then create a parent and let the parent load
404 # itself from the remainder of the string
405 if len(parts) > 1 and parts[1] != '':
406 self.parent = self.__class__()
407 self.parent.load_from_string(parts[1])
410 # Load the certificate from a file
412 def load_from_file(self, filename):
413 file = open(filename)
415 self.load_from_string(string)
416 self.filename=filename
419 # Save the certificate to a string.
421 # @param save_parents If save_parents==True, then also save the parent certificates.
423 def save_to_string(self, save_parents=True):
424 if self.x509 is None:
425 logger.warn("None cert in certificate.save_to_string")
427 string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.x509)
428 if save_parents and self.parent:
429 string = string + self.parent.save_to_string(save_parents)
433 # Save the certificate to a file.
434 # @param save_parents If save_parents==True, then also save the parent certificates.
436 def save_to_file(self, filename, save_parents=True, filep=None):
437 string = self.save_to_string(save_parents=save_parents)
441 f = open(filename, 'w')
444 self.filename=filename
447 # Save the certificate to a random file in /tmp/
448 # @param save_parents If save_parents==True, then also save the parent certificates.
449 def save_to_random_tmp_file(self, save_parents=True):
450 fp, filename = mkstemp(suffix='cert', text=True)
451 fp = os.fdopen(fp, "w")
452 self.save_to_file(filename, save_parents=True, filep=fp)
456 # Sets the issuer private key and name
457 # @param key Keypair object containing the private key of the issuer
458 # @param subject String containing the name of the issuer
459 # @param cert (optional) Certificate object containing the name of the issuer
461 def set_issuer(self, key, subject=None, cert=None):
464 # it's a mistake to use subject and cert params at the same time
466 if isinstance(subject, dict) or isinstance(subject, str):
467 req = crypto.X509Req()
468 reqSubject = req.get_subject()
469 if (isinstance(subject, dict)):
470 for key in reqSubject.keys():
471 setattr(reqSubject, key, subject[key])
473 setattr(reqSubject, "CN", subject)
475 # subject is not valid once req is out of scope, so save req
478 # if a cert was supplied, then get the subject from the cert
479 subject = cert.x509.get_subject()
481 self.issuerSubject = subject
484 # Get the issuer name
486 def get_issuer(self, which="CN"):
487 x = self.x509.get_issuer()
488 return getattr(x, which)
491 # Set the subject name of the certificate
493 def set_subject(self, name):
494 req = crypto.X509Req()
495 subj = req.get_subject()
496 if (isinstance(name, dict)):
497 for key in name.keys():
498 setattr(subj, key, name[key])
500 setattr(subj, "CN", name)
501 self.x509.set_subject(subj)
504 # Get the subject name of the certificate
506 def get_subject(self, which="CN"):
507 x = self.x509.get_subject()
508 return getattr(x, which)
511 # Get a pretty-print subject name of the certificate
512 # let's try to make this a little more usable as is makes logs hairy
513 # FIXME: Consider adding 'urn:publicid' and 'uuid' back for GENI?
514 pretty_fields = ['email']
515 def filter_chunk(self, chunk):
516 for field in self.pretty_fields:
520 def pretty_cert(self):
522 x = self.x509.get_subject()
523 ou = getattr(x, "OU")
524 if ou: message += " OU: {}".format(ou)
525 cn = getattr(x, "CN")
526 if cn: message += " CN: {}".format(cn)
527 data = self.get_data(field='subjectAltName')
529 message += " SubjectAltName:"
531 filtered = [self.filter_chunk(chunk) for chunk in data.split()]
532 message += " ".join( [f for f in filtered if f])
533 omitted = len ([f for f in filtered if not f])
535 message += "..+{} omitted".format(omitted)
540 # Get the public key of the certificate.
542 # @param key Keypair object containing the public key
544 def set_pubkey(self, key):
545 assert(isinstance(key, Keypair))
546 self.x509.set_pubkey(key.get_openssl_pkey())
549 # Get the public key of the certificate.
550 # It is returned in the form of a Keypair object.
552 def get_pubkey(self):
553 m2x509 = X509.load_cert_string(self.save_to_string())
555 pkey.key = self.x509.get_pubkey()
556 pkey.m2key = m2x509.get_pubkey()
559 def set_intermediate_ca(self, val):
560 return self.set_is_ca(val)
562 # Set whether this cert is for a CA. All signers and only signers should be CAs.
563 # The local member starts unset, letting us check that you only set it once
564 # @param val Boolean indicating whether this cert is for a CA
565 def set_is_ca(self, val):
569 if self.isCA != None:
570 # Can't double set properties
571 raise Exception, "Cannot set basicConstraints CA:?? more than once. Was %s, trying to set as %s" % (self.isCA, val)
575 self.add_extension('basicConstraints', 1, 'CA:TRUE')
577 self.add_extension('basicConstraints', 1, 'CA:FALSE')
582 # Add an X509 extension to the certificate. Add_extension can only be called
583 # once for a particular extension name, due to limitations in the underlying
586 # @param name string containing name of extension
587 # @param value string containing value of the extension
589 def add_extension(self, name, critical, value):
592 oldExtVal = self.get_extension(name)
594 # M2Crypto LookupError when the extension isn't there (yet)
597 # This code limits you from adding the extension with the same value
598 # The method comment says you shouldn't do this with the same name
599 # But actually it (m2crypto) appears to allow you to do this.
600 if oldExtVal and oldExtVal == value:
601 # don't add this extension again
602 # just do nothing as here
604 # FIXME: What if they are trying to set with a different value?
605 # Is this ever OK? Or should we raise an exception?
607 # raise "Cannot add extension %s which had val %s with new val %s" % (name, oldExtVal, value)
609 ext = crypto.X509Extension (name, critical, value)
610 self.x509.add_extensions([ext])
613 # Get an X509 extension from the certificate
615 def get_extension(self, name):
620 certstr = self.save_to_string()
621 if certstr is None or certstr == "":
623 # pyOpenSSL does not have a way to get extensions
624 m2x509 = X509.load_cert_string(certstr)
626 logger.warn("No cert loaded in get_extension")
628 if m2x509.get_ext(name) is None:
630 value = m2x509.get_ext(name).get_value()
635 # Set_data is a wrapper around add_extension. It stores the parameter str in
636 # the X509 subject_alt_name extension. Set_data can only be called once, due
637 # to limitations in the underlying library.
639 def set_data(self, str, field='subjectAltName'):
640 # pyOpenSSL only allows us to add extensions, so if we try to set the
641 # same extension more than once, it will not work
642 if self.data.has_key(field):
643 raise "Cannot set ", field, " more than once"
644 self.data[field] = str
645 self.add_extension(field, 0, str)
648 # Return the data string that was previously set with set_data
650 def get_data(self, field='subjectAltName'):
651 if self.data.has_key(field):
652 return self.data[field]
655 uri = self.get_extension(field)
656 self.data[field] = uri
660 return self.data[field]
663 # Sign the certificate using the issuer private key and issuer subject previous set with set_issuer().
666 logger.debug('certificate.sign')
667 assert self.x509 != None
668 assert self.issuerSubject != None
669 assert self.issuerKey != None
670 self.x509.set_issuer(self.issuerSubject)
671 self.x509.sign(self.issuerKey.get_openssl_pkey(), self.digest)
674 # Verify the authenticity of a certificate.
675 # @param pkey is a Keypair object representing a public key. If Pkey
676 # did not sign the certificate, then an exception will be thrown.
678 def verify(self, pubkey):
679 # pyOpenSSL does not have a way to verify signatures
680 m2x509 = X509.load_cert_string(self.save_to_string())
681 m2pubkey = pubkey.get_m2_pubkey()
683 # verify returns -1 or 0 on failure depending on how serious the
684 # error conditions are
685 return m2x509.verify(m2pubkey) == 1
687 # XXX alternatively, if openssl has been patched, do the much simpler:
689 # self.x509.verify(pkey.get_openssl_key())
695 # Return True if pkey is identical to the public key that is contained in the certificate.
696 # @param pkey Keypair object
698 def is_pubkey(self, pkey):
699 return self.get_pubkey().is_same(pkey)
702 # Given a certificate cert, verify that this certificate was signed by the
703 # public key contained in cert. Throw an exception otherwise.
705 # @param cert certificate object
707 def is_signed_by_cert(self, cert):
708 k = cert.get_pubkey()
709 result = self.verify(k)
713 # Set the parent certficiate.
715 # @param p certificate object.
717 def set_parent(self, p):
721 # Return the certificate object of the parent of this certificate.
723 def get_parent(self):
727 # Verification examines a chain of certificates to ensure that each parent
728 # signs the child, and that some certificate in the chain is signed by a
729 # trusted certificate.
731 # Verification is a basic recursion: <pre>
732 # if this_certificate was signed by trusted_certs:
735 # return verify_chain(parent, trusted_certs)
738 # At each recursion, the parent is tested to ensure that it did sign the
739 # child. If a parent did not sign a child, then an exception is thrown. If
740 # the bottom of the recursion is reached and the certificate does not match
741 # a trusted root, then an exception is thrown.
742 # Also require that parents are CAs.
744 # @param Trusted_certs is a list of certificates that are trusted.
747 def verify_chain(self, trusted_certs = None):
748 # Verify a chain of certificates. Each certificate must be signed by
749 # the public key contained in it's parent. The chain is recursed
750 # until a certificate is found that is signed by a trusted root.
752 # verify expiration time
753 if self.x509.has_expired():
754 if debug_verify_chain:
755 logger.debug("verify_chain: NO, Certificate %s has expired" % self.pretty_cert())
756 raise CertExpired(self.pretty_cert(), "client cert")
758 # if this cert is signed by a trusted_cert, then we are set
759 for trusted_cert in trusted_certs:
760 if self.is_signed_by_cert(trusted_cert):
761 # verify expiration of trusted_cert ?
762 if not trusted_cert.x509.has_expired():
763 if debug_verify_chain:
764 logger.debug("verify_chain: YES. Cert %s signed by trusted cert %s"%(
765 self.pretty_cert(), trusted_cert.pretty_cert()))
768 if debug_verify_chain:
769 logger.debug("verify_chain: NO. Cert %s is signed by trusted_cert %s, but that signer is expired..."%(
770 self.pretty_cert(),trusted_cert.pretty_cert()))
771 raise CertExpired(self.pretty_cert()," signer trusted_cert %s"%trusted_cert.pretty_cert())
773 # if there is no parent, then no way to verify the chain
775 if debug_verify_chain:
776 logger.debug("verify_chain: NO. %s has no parent and issuer %s is not in %d trusted roots"%\
777 (self.pretty_cert(), self.get_issuer(), len(trusted_certs)))
778 raise CertMissingParent(self.pretty_cert() + \
779 ": Issuer %s is not one of the %d trusted roots, and cert has no parent." %\
780 (self.get_issuer(), len(trusted_certs)))
782 # if it wasn't signed by the parent...
783 if not self.is_signed_by_cert(self.parent):
784 if debug_verify_chain:
785 logger.debug("verify_chain: NO. %s is not signed by parent %s, but by %s"%\
787 self.parent.pretty_cert(),
789 raise CertNotSignedByParent("%s: Parent %s, issuer %s"\
790 % (self.pretty_cert(),
791 self.parent.pretty_cert(),
794 # Confirm that the parent is a CA. Only CAs can be trusted as
796 # Note that trusted roots are not parents, so don't need to be
798 # Ugly - cert objects aren't parsed so we need to read the
799 # extension and hope there are no other basicConstraints
800 if not self.parent.isCA and not (self.parent.get_extension('basicConstraints') == 'CA:TRUE'):
801 logger.warn("verify_chain: cert %s's parent %s is not a CA" % \
802 (self.pretty_cert(), self.parent.pretty_cert()))
803 raise CertNotSignedByParent("%s: Parent %s not a CA" % (self.pretty_cert(),
804 self.parent.pretty_cert()))
806 # if the parent isn't verified...
807 if debug_verify_chain:
808 logger.debug("verify_chain: .. %s, -> verifying parent %s"%\
809 (self.pretty_cert(),self.parent.pretty_cert()))
810 self.parent.verify_chain(trusted_certs)
814 ### more introspection
815 def get_extensions(self):
816 # pyOpenSSL does not have a way to get extensions
818 m2x509 = X509.load_cert_string(self.save_to_string())
819 nb_extensions = m2x509.get_ext_count()
820 logger.debug("X509 had %d extensions"%nb_extensions)
821 for i in range(nb_extensions):
822 ext=m2x509.get_ext_at(i)
823 triples.append( (ext.get_name(), ext.get_value(), ext.get_critical(),) )
826 def get_data_names(self):
827 return self.data.keys()
829 def get_all_datas (self):
830 triples = self.get_extensions()
831 for name in self.get_data_names():
832 triples.append( (name,self.get_data(name),'data',) )
836 def get_filename(self):
837 return getattr(self,'filename',None)
839 def dump (self, *args, **kwargs):
840 print self.dump_string(*args, **kwargs)
842 def dump_string (self,show_extensions=False):
844 result += "CERTIFICATE for %s\n"%self.pretty_cert()
845 result += "Issued by %s\n"%self.get_issuer()
846 filename=self.get_filename()
847 if filename: result += "Filename %s\n"%filename
849 all_datas = self.get_all_datas()
850 result += " has %d extensions/data attached"%len(all_datas)
851 for (n, v, c) in all_datas:
853 result += " data: %s=%s\n"%(n,v)
855 result += " ext: %s (crit=%s)=<<<%s>>>\n"%(n,c,v)