#!/usr/local/bin/python import os, sys import subprocess import tempfile import logging from M2Crypto import SSL, X509, EVP from pyasn1.codec.der import decoder # The version of M2Crypto on users is pretty old and doesn't have all the # features that are useful. The legacy code is somewhat more brittle than the # main line, but will work. if "as_der" not in dir(EVP.PKey): from asn1_raw import get_key_bits_from_file, get_key_bits_from_cert legacy = True else: legacy = False class fedid: """ Wrapper around the federated ID from an X509 certificate. """ HASHSIZE=20 def __init__(self, bits=None, hexstr=None, cert=None, file=None, certstr=None): if bits != None: self.set_bits(bits) elif hexstr != None: self.set_hexstr(hexstr) elif cert != None: self.set_cert(cert) elif file != None: self.set_file(file) elif certstr: self.set_certstr(certstr) else: self.buf = None def __hash__(self): return hash(self.buf) def __eq__(self, other): if isinstance(other, type(self)): return self.buf == other.buf elif isinstance(other, type(str())): return self.buf == other; else: return False def __ne__(self, other): return not self.__eq__(other) def __str__(self): if self.buf != None: return str().join([ "%02x" % ord(x) for x in self.buf]) else: return "" def __repr__(self): return "fedid(hexstr='%s')" % self.__str__() def pack_soap(self): return self.buf def pack_xmlrpc(self): return self.buf def digest_bits(self, bits): """Internal function. Compute the fedid from bits and store in buf""" d = EVP.MessageDigest('sha1') d.update(bits) self.buf = d.final() def set_hexstr(self, hexstr): h = hexstr.replace(':','') self.buf= str().join([chr(int(h[i:i+2],16)) \ for i in range(0,2*fedid.HASHSIZE,2)]) def get_hexstr(self): """Return the hexstring representation of the fedid""" return self.__str__() def set_bits(self, bits): """Set the fedid to bits(a 160 bit buffer)""" self.buf = bits def get_bits(self): """Get the 160 bit buffer from the fedid""" return self.buf def set_certstr(self, certstr): """ Certstr is the contents of a certificate file. Unfortunately we basically have to stuff it back into a temp file to read it. """ tf = tempfile.NamedTemporaryFile() tf.write(certstr) tf.flush() self.set_file(tf.name) tf.close() def set_file(self, file): """Get the fedid from a certificate file Calculate the SHA1 hash over the bit string of the public key as defined in RFC3280. """ self.buf = None if legacy: self.digest_bits(get_key_bits_from_file(file)) else: self.set_cert(X509.load_cert(file)) def set_cert(self, cert): """Get the fedid from a certificate. Calculate the SHA1 hash over the bit string of the public key as defined in RFC3280. """ self.buf = None if (cert != None): if legacy: self.digest_bits(get_key_bits_from_cert(cert)) else: b = [] k = cert.get_pubkey() # Getting the key was easy, but getting the bit string of the # key requires a side trip through ASN.1 dec = decoder.decode(k.as_der()) # kv is a tuple of the bits in the key. The loop below # recombines these into bytes and then into a buffer for the # SSL digest function. kv = dec[0].getComponentByPosition(1) for i in range(0, len(kv), 8): v = 0 for j in range(0, 8): v = (v << 1) + kv[i+j] b.append(v) # The comprehension turns b from a list of bytes into a buffer # (string) of bytes self.digest_bits(str().join([chr(x) for x in b])) def generate_fedid(subj, bits=2048, log=None, dir=None, trace=sys.stderr, ssl_prog="/usr/bin/openssl"): """ Create a new certificate and derive a fedid from it. The fedid and the certificate are returned as a tuple. """ keypath = None certpath = None try: try: kd, keypath = tempfile.mkstemp(dir=dir, prefix="key", suffix=".pem") cd, certpath = tempfile.mkstemp(dir=dir, prefix="cert", suffix=".pem") cmd = [ssl_prog, "req", "-text", "-newkey", "rsa:%d" % bits, "-keyout", keypath, "-nodes", "-subj", "/CN=%s" % subj, "-x509", "-days", "30", "-out", certpath] if log: log.debug("[generate_fedid] %s" % " ".join(cmd)) if trace: call_out = trace else: call_out = open("/dev/null", "w") rv = subprocess.call(cmd, stdout=call_out, stderr=call_out) if log: log.debug("rv = %d" % rv) if rv == 0: cert = "" for p in (certpath, keypath): f = open(p) for line in f: cert += line fid = fedid(file=certpath) return (fid, cert) else: return (None, None) except EnvironmentError, e: raise e finally: if keypath: os.remove(keypath) if certpath: os.remove(certpath)