source: fedd/fedd_util.py @ 9460b1e

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

move remote_service out of fedd_util

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