""" x509.py - X.509 certificate objects (c) by Michael Stroeder This module is distributed under the terms of the GPL (GNU GENERAL PUBLIC LICENSE) Version 2 (see http://www.gnu.org/copyleft/gpl.html) $Id: x509.py,v 1.10 2003/08/21 08:47:07 michael Exp $ """ # Python standard lib import sys, string, base64, md5, sha # Pisces from pisces import asn1 # mspki itself import utctime, util, x500, asn1helper class Attribute(asn1.ASN1Object): """ Base class for all attributes and extensions. Do not use directly! Just for saving typing methods again and again... """ def __init__(self,val): self.val = val def __repr__(self): return '' % (self.__class__.__name__,self) class Version(Attribute): """[0] EXPLICIT Version DEFAULT v1""" def __int__(self): if self.val==None: return 1 else: return int(self.val)+1 def __str__(self): if self.val==None: return str(1) else: return '%d (0x%X)' % (self.val+1,self.val) class CertificateSerialNumber(Attribute): """CertificateSerialNumber ::= INTEGER""" def __init__(self,val): if type(val)==type(''): val = util.bytestolong(val) self.val = val def __int__(self): return int(self.val) def __hex__(self): return hex(self.val) def __str__(self): if self.val!=None: return '%d (0x%X)' % (self.val,self.val) else: return repr(None) class X509SignedObject: """ Base class for X.509 certificates and CRLs This class should not be used directly! """ def __init__(self,buf,inform='der'): inform = string.lower(inform) if inform == 'der': # Binary format according to DER self.val = asn1.parse(buf) elif inform == 'pem': # Base64-encoded DER cert wrapped with # -----BEGIN (CERTIFICATE|CRL)----- # [...] # -----END (CERTIFICATE|CRL)----- self.val = asn1.parse(util.pem2der(buf)) elif inform == 'base64': # Base64-encoded DER cert self.val = asn1.parse(base64.decodestring(string.strip(buf))) else: raise ( ValueError, "Value %s for parameter inform invalid. Must be either DER, PEM or BASE64." % (inform) ) def signatureAlgorithm(self,oids=None): """Algorithm used when creating signature""" if oids: return asn1helper.GetOIDDescription(self.val[1].val[0],oids) else: return self.val[1].val[0] def signatureValue(self): """Certificate's signature value""" return self.val[2].val class Certificate(X509SignedObject): """ Class for X.509 certificates Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, signatureAlgorithm AlgorithmIdentifier, signatureValue BIT STRING } TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, serialNumber CertificateSerialNumber, signature AlgorithmIdentifier, issuer Name, validity Validity, subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo, issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version shall be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version shall be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL -- If present, version shall be v3 } """ def __init__(self,buf,inform='der'): X509SignedObject.__init__(self,buf,inform) # Nested object with certficate data self.tbsCertificate = self.val[0] self.md5_digest = md5.new(buf).digest() self.sha1_digest = sha.new(buf).digest() # Try to determine if optional version field is present # FIX ME!!! This is a pretty ugly hack! if hasattr(self.tbsCertificate[0],'tag') and \ self.tbsCertificate[0].tag==0: # no version number present self.__version__ = Version(self.tbsCertificate[0].val) self.__tbsoffset__ = 1 else: # version number present and encoded in contextual object self.__version__ = Version(None) self.__tbsoffset__ = 0 def version(self): """X.509 certificate version number as integer""" return self.__version__ def serialNumber(self): """Certificate's serial number as long integer""" return CertificateSerialNumber(self.tbsCertificate[self.__tbsoffset__+0]) def signature(self,oids=None): """Certificate's signature""" if oids: return asn1helper.GetOIDDescription(self.tbsCertificate[self.__tbsoffset__+1].val[0],oids) else: return self.tbsCertificate[self.__tbsoffset__+1].val[0] def issuer(self): """Issuer's distinguished name""" return x500.Name(self.tbsCertificate[self.__tbsoffset__+2]) def validity(self): """ Returns tuple (notBefore,notAfter) notBefore, notAfter are instances of utctime.UTCTime containing UTCTime of begin and end of validity period. """ return ( utctime.UTCTime(self.tbsCertificate[self.__tbsoffset__+3][0].val), utctime.UTCTime(self.tbsCertificate[self.__tbsoffset__+3][1].val) ) def subject(self): """Subject's distinguished name""" return x500.Name(self.tbsCertificate[self.__tbsoffset__+4]) def subjectPublicKeyInfo(self,oids=None): """Subject's public key""" if oids: alg = asn1helper.GetOIDDescription(self.tbsCertificate[self.__tbsoffset__+5].val[0].val[0],oids) else: alg = self.tbsCertificate[self.__tbsoffset__+5].val[0].val[0] modulus, publicExponent = asn1.parse(self.tbsCertificate[self.__tbsoffset__+5].val[1].val) return (alg, modulus, publicExponent) def MD5Fingerprint(self,delimiter=':'): """MD5 fingerprint in dotted notation (delimiter between bytes)""" return util.HexString(self.md5_digest,delimiter) def SHA1Fingerprint(self,delimiter=':'): """Return SHA-1 fingerprint in dotted notation (delimiter between bytes)""" return util.HexString(self.sha1_digest,delimiter) def issuerUniqueID(self): """Get subjectUniqueID (tag 1)""" for i in range(self.__tbsoffset__+6,len(self.tbsCertificate)): if isinstance(self.tbsCertificate[i],asn1.Contextual): if self.tbsCertificate[i].tag==1: return self.tbsCertificate[i] return None def subjectUniqueID(self): """Get subjectUniqueID (tag 2)""" for i in range(self.__tbsoffset__+6,len(self.tbsCertificate)): if isinstance(self.tbsCertificate[i],asn1.Contextual): if self.tbsCertificate[i].tag==2: return self.tbsCertificate[i] return None def as_text(self,oids=None): """Try to mimique the as_text() output of OpenSSL""" notBefore,notAfter = self.validity() subjectPublicKeyAlg, subjectPublicKeyModulus, subjectPublicKeyExponent = self.subjectPublicKeyInfo(oids) subjectPublicKeyModulus_str = util.longtobytes(subjectPublicKeyModulus,128) serialNumber = self.serialNumber() return """Certificate: Data: Version: %s Serial Number: %s Signature Algorithm: %s Issuer: %s Validity Not Before: %s Not After : %s Subject: %s Subject Public Key Info: Public Key Algorithm: %s RSA Public Key: (%d bit) Modulus (%d bit): %s Exponent: %d (0x%X) Signature Algorithm: %s %s """ % ( self.version(), serialNumber, self.signature(oids), self.issuer().__str__(oids), notBefore, notAfter, self.subject().__str__(oids), subjectPublicKeyAlg, 8*len(subjectPublicKeyModulus_str),8*len(subjectPublicKeyModulus_str), util.HexString({0:'\000',1:'\001'}[subjectPublicKeyModulus<0L]+subjectPublicKeyModulus_str,wrap=66,indent=20), subjectPublicKeyExponent,subjectPublicKeyExponent, self.signatureAlgorithm(oids), util.HexString(self.signatureValue(),wrap=64,indent=8) ) class CRL(X509SignedObject): """ Class for X.509 CRLs CertificateList ::= SEQUENCE { tbsCertList TBSCertList, signatureAlgorithm AlgorithmIdentifier, signatureValue BIT STRING } TBSCertList ::= SEQUENCE { version Version OPTIONAL, -- if present, shall be v2 signature AlgorithmIdentifier, issuer Name, thisUpdate Time, nextUpdate Time OPTIONAL, revokedCertificates SEQUENCE OF SEQUENCE { userCertificate CertificateSerialNumber, revocationDate Time, crlEntryExtensions Extensions OPTIONAL -- if present, shall be v2 } OPTIONAL, crlExtensions [0] EXPLICIT Extensions OPTIONAL -- if present, shall be v2 } """ def __init__(self,buf,inform='der'): X509SignedObject.__init__(self,buf,inform) # Nested object with CRL data self.tbsCertList = self.val[0] # Try to determine if optional version field is present # FIX ME!!! This is a pretty ugly hack! if isinstance(self.tbsCertList[0],asn1.Sequence): self.__version__ = Version(None) self.__tbsoffset__ = 0 else: self.__version__ = Version(self.tbsCertList[0]) self.__tbsoffset__ = 1 def version(self): """X.509 CRL version number as integer""" return self.__version__ def signature(self,oids=None): """Certificate's signature algorithm""" if oids: return asn1helper.GetOIDDescription( self.tbsCertList[self.__tbsoffset__+0].val[0], oids ) else: return self.tbsCertList[self.__tbsoffset__+0].val[0] def issuer(self): """Issuer's distinguished name""" return x500.Name(self.tbsCertList[self.__tbsoffset__+1]) def thisUpdate(self): """Returns time tuple of thisUpdate""" return utctime.UTCTime(self.tbsCertList[self.__tbsoffset__+2].val) def nextUpdate(self): """Returns utctime.UTCTime of nextUpdate if present, None else""" if isinstance(self.tbsCertList[self.__tbsoffset__+3],asn1.UTCTime) or \ isinstance(self.tbsCertList[self.__tbsoffset__+3],asn1.GeneralizedTime): return utctime.UTCTime(self.tbsCertList[self.__tbsoffset__+3].val) else: return None def revokedCertificates(self): """ Get list of revoked certificates. Each list member is a tuple ( userCertificate, # serial number of revoked certificate # as long integer revocationDate, # time tuple of revocation timestamp crlEntryExtensions # optional (None if not present) ) """ revokeList = [] if len (self.tbsCertList)>=self.__tbsoffset__+4+1 and \ (not hasattr(self.tbsCertList[self.__tbsoffset__+4],'tag') or self.tbsCertList[self.__tbsoffset__+4].tag!=0): for i in self.tbsCertList[self.__tbsoffset__+4].val: i_len = len(i) if i_len in [2,3]: userCertificate = i[0] revocationDate = utctime.UTCTime(str(i[1].val)) else: raise ( ValueError, "Item in revokedCertificates list has invalid length (%d)." % (i_len) ) if i_len==3: crlEntryExtensions = i[2] else: crlEntryExtensions = None revokeList.append((userCertificate,revocationDate,crlEntryExtensions)) return revokeList def as_text(self,oids=None): revokeList = self.revokedCertificates() if revokeList: revokeList_str = string.join( map( lambda x: ' Serial Number: %d\n Revocation Date: %s' % (x[0],x[1]), revokeList ), '\n' ) else: revokeList_str = 'None' return """Certificate Revocation List (CRL): Version %X Signature Algorithm: %s Issuer: %s Last Update: %s Next Update: %s Revoked Certificates: %s Signature Algorithm: %s %s """ % ( self.version(), self.signature(oids), self.issuer().__str__(oids), self.thisUpdate(), self.nextUpdate(), revokeList_str, self.signatureAlgorithm(oids), util.HexString(self.signatureValue(),wrap=64,indent=8) )