source: fedd/fedd_util.py @ 4fc2250

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since 4fc2250 was 4fc2250, checked in by Ted Faber <faber@…>, 16 years ago

add slice/experiment name

  • Property mode set to 100644
File size: 7.6 KB
Line 
1#!/usr/local/bin/python
2
3import os, sys
4import subprocess
5import tempfile
6
7from M2Crypto import SSL, X509, EVP
8from pyasn1.codec.der import decoder
9
10
11# The version of M2Crypto on users is pretty old and doesn't have all the
12# features that are useful.  The legacy code is somewhat more brittle than the
13# main line, but will work.
14if "as_der" not in dir(EVP.PKey):
15    from asn1_raw import get_key_bits_from_file, get_key_bits_from_cert
16    legacy = True
17else:
18    legacy = False
19
20class fedd_ssl_context(SSL.Context):
21    """
22    Simple wrapper around an M2Crypto.SSL.Context to initialize it for fedd.
23    """
24    def __init__(self, my_cert, trusted_certs=None, password=None):
25        """
26        construct a fedd_ssl_context
27
28        @param my_cert: PEM file with my certificate in it
29        @param trusted_certs: PEM file with trusted certs in it (optional)
30        """
31        SSL.Context.__init__(self)
32
33        # load_cert takes a callback to get a password, not a password, so if
34        # the caller provided a password, this creates a nonce callback using a
35        # lambda form.
36        if password != None and not callable(password):
37            # This is cute.  password = lambda *args: password produces a
38            # function object that returns itself rather than one that returns
39            # the object itself.  This is because password is an object
40            # reference and after the assignment it's a lambda.  So we assign
41            # to a temp.
42            pwd = password
43            password =lambda *args: pwd
44
45        if password != None:
46            self.load_cert(my_cert, callback=password)
47        else:
48            self.load_cert(my_cert)
49        if trusted_certs != None: self.load_verify_locations(trusted_certs)
50        self.set_verify(SSL.verify_peer, 10)
51
52class fedid:
53    """
54    Wrapper around the federated ID from an X509 certificate.
55    """
56    HASHSIZE=20
57    def __init__(self, bits=None, hexstr=None, cert=None, file=None):
58        if bits != None:
59            self.set_bits(bits)
60        elif hexstr != None:
61            self.set_hexstr(hexstr)
62        elif cert != None:
63            self.set_cert(cert)
64        elif file != None:
65            self.set_file(file)
66        else:
67            self.buf = None
68
69    def __hash__(self):
70        return hash(self.buf)
71
72    def __eq__(self, other):
73        if isinstance(other, type(self)):
74            return self.buf == other.buf
75        elif isinstance(other, type(str())):
76            return self.buf == other;
77        else:
78            return False
79
80    def __ne__(self, other): return not self.__eq__(other)
81
82    def __str__(self):
83        if self.buf != None:
84            return str().join([ "%02x" % ord(x) for x in self.buf])
85        else: return ""
86
87    def __repr__(self):
88        return "fedid(hexstr='%s')" % self.__str__()
89
90    def pack_soap(self):
91        return self.buf
92
93    def digest_bits(self, bits):
94        """Internal function.  Compute the fedid from bits and store in buf"""
95        d = EVP.MessageDigest('sha1')
96        d.update(bits)
97        self.buf = d.final()
98
99
100    def set_hexstr(self, hexstr):
101        h = hexstr.replace(':','')
102        self.buf= str().join([chr(int(h[i:i+2],16)) \
103                for i in range(0,2*fedid.HASHSIZE,2)])
104
105    def get_hexstr(self):
106        """Return the hexstring representation of the fedid"""
107        return __str__(self)
108
109    def set_bits(self, bits):
110        """Set the fedid to bits(a 160 bit buffer)"""
111        self.buf = bits
112
113    def get_bits(self):
114        """Get the 160 bit buffer from the fedid"""
115        return self.buf
116
117    def set_file(self, file):
118        """Get the fedid from a certificate file
119
120        Calculate the SHA1 hash over the bit string of the public key as
121        defined in RFC3280.
122        """
123        self.buf = None
124        if legacy: self.digest_bits(get_key_bits_from_file(file))
125        else: self.set_cert(X509.load_cert(file))
126
127    def set_cert(self, cert):
128        """Get the fedid from a certificate.
129
130        Calculate the SHA1 hash over the bit string of the public key as
131        defined in RFC3280.
132        """
133
134        self.buf = None
135        if (cert != None):
136            if legacy:
137                self.digest_bits(get_key_bits_from_cert(cert))
138            else:
139                b = []
140                k = cert.get_pubkey()
141
142                # Getting the key was easy, but getting the bit string of the
143                # key requires a side trip through ASN.1
144                dec = decoder.decode(k.as_der())
145
146                # kv is a tuple of the bits in the key.  The loop below
147                # recombines these into bytes and then into a buffer for the
148                # SSL digest function.
149                kv =  dec[0].getComponentByPosition(1)
150                for i in range(0, len(kv), 8):
151                    v = 0
152                    for j in range(0, 8):
153                        v = (v << 1) + kv[i+j]
154                    b.append(v)
155                # The comprehension turns b from a list of bytes into a buffer
156                # (string) of bytes
157                self.digest_bits(str().join([chr(x) for x in b]))
158
159def pack_id(id):
160    """
161    Return a dictionary with the field name set by the id type.  Handy for
162    creating dictionaries to be converted to messages.
163    """
164    if isinstance(id, type(fedid())): return { 'fedid': id }
165    elif id.startswith("http:") or id.startswith("https:"): return { 'uri': id }
166    else: return { 'username': id}
167
168def unpack_id(id):
169    """return id as a type determined by the key"""
170    if id.has_key("fedid"): return fedid(id["fedid"])
171    else:
172        for k in ("username", "uri", "kerberosUsername"):
173            if id.has_key(k): return id[k]
174    return None
175
176def pack_soap(container, name, contents):
177    """
178    Convert the dictionary in contents into a tree of ZSI classes.
179
180    The holder classes are constructed from factories in container and assigned
181    to either the element or attribute name.  This is used to recursively
182    create the SOAP message.
183    """
184    if getattr(contents, "__iter__", None) != None:
185        attr =getattr(container, "new_%s" % name, None)
186        if attr: obj = attr()
187        else:
188            print dir(container)
189            raise TypeError("%s does not have a new_%s attribute" % \
190                    (container, name))
191        for e, v in contents.iteritems():
192            assign = getattr(obj, "set_element_%s" % e, None) or \
193                    getattr(obj, "set_attribute_%s" % e, None)
194            if isinstance(v, type(dict())):
195                assign(pack_soap(obj, e, v))
196            elif getattr(v, "__iter__", None) != None:
197                assign([ pack_soap(obj, e, val ) for val in v])
198            elif getattr(v, "pack_soap", None) != None:
199                assign(v.pack_soap())
200            else:
201                assign(v)
202        return obj
203    else: return contents
204
205def unpack_soap(element):
206    """
207    Convert a tree of ZSI SOAP classes intro a hash.  The inverse of pack_soap
208
209    Elements or elements that are empty are ignored.
210    """
211    methods = [ m for m in dir(element) \
212            if m.startswith("get_element") or m.startswith("get_attribute")]
213    if len(methods) > 0:
214        rv = { }
215        for m in methods:
216            if m.startswith("get_element_"): n = m.replace("get_element_","",1)
217            else: n = m.replace("get_attribute_", "", 1)
218            sub = getattr(element, m)()
219            if sub != None:
220                if isinstance(sub, basestring):
221                    rv[n] = sub
222                elif getattr(sub, "__iter__", None) != None:
223                    if len(sub) > 0: rv[n] = [unpack_soap(e) for e in sub]
224                else:
225                    rv[n] = unpack_soap(sub)
226        return rv
227    else: 
228        return element
229
230def generate_fedid(subj, bits=2048, trace=None, dir=None):
231    """
232    Create a new certificate and derive a fedid from it.
233
234    The fedid and the certificte are returned as a tuple.
235    """
236
237    keypath = None
238    certpath = None
239    try:
240        try:
241            kd, keypath = tempfile.mkstemp(dir=dir, prefix="key",
242                    suffix=".pem")
243            cd, certpath = tempfile.mkstemp(dir=dir, prefix="cert",
244                    suffix=".pem")
245
246            cmd = ["openssl", "req", "-text", "-newkey", "rsa:%d" % bits, 
247                    "-keyout", keypath,  "-nodes", "-subj", "/CN=%s" % subj,
248                    "-x509", "-days", "30", "-out", certpath]
249
250            if trace:
251                print >>trace, "calling %s" % " ".join(cmd)
252                call_out = trace
253            else:
254                call_out = open("/dev/null", "w")
255               
256            rv = subprocess.call(cmd, stdout=call_out, stderr=call_out)
257            if rv == 0:
258                cert = ""
259                for p in (certpath, keypath):
260                    f = open(p)
261                    for line in f:
262                        cert += line
263               
264                fid = fedid(file=certpath)
265                return (fid, cert)
266            else:
267                return None
268        except IOError, e:
269            raise e
270    finally:
271        if keypath: os.remove(keypath)
272        if certpath: os.remove(certpath)
Note: See TracBrowser for help on using the repository browser.