source: fedd/fedd_util.py @ 0a47d52

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

XMLRPC proxy from SOAP and XML

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