source: fedd/deter/fedid.py @ dffa585

Last change on this file since dffa585 was 4bfc015, checked in by Ted Faber <faber@…>, 11 years ago

Make nonce fedids last a year.

  • Property mode set to 100644
File size: 4.7 KB
Line 
1#!/usr/local/bin/python
2
3import os, sys
4import subprocess
5import tempfile
6import logging
7
8from M2Crypto import SSL, X509, EVP
9from pyasn1.codec.der import decoder
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 fedid:
21    """
22    Wrapper around the federated ID from an X509 certificate.
23    """
24    HASHSIZE=20
25    def __init__(self, bits=None, hexstr=None, cert=None, file=None, 
26            certstr=None):
27        if bits != None:
28            self.set_bits(bits)
29        elif hexstr != None:
30            self.set_hexstr(hexstr)
31        elif cert != None:
32            self.set_cert(cert)
33        elif file != None:
34            self.set_file(file)
35        elif certstr:
36            self.set_certstr(certstr)
37        else:
38            self.buf = None
39
40    def __hash__(self):
41        return hash(self.buf)
42
43    def __eq__(self, other):
44        if isinstance(other, type(self)):
45            return self.buf == other.buf
46        elif isinstance(other, type(str())):
47            return self.buf == other;
48        else:
49            return False
50
51    def __ne__(self, other): return not self.__eq__(other)
52
53    def __str__(self):
54        if self.buf != None:
55            return str().join([ "%02x" % ord(x) for x in self.buf])
56        else: return ""
57
58    def __repr__(self):
59        return "fedid(hexstr='%s')" % self.__str__()
60
61    def pack_soap(self):
62        return self.buf
63
64    def pack_xmlrpc(self):
65        return self.buf
66
67    def digest_bits(self, bits):
68        """Internal function.  Compute the fedid from bits and store in buf"""
69        d = EVP.MessageDigest('sha1')
70        d.update(bits)
71        self.buf = d.final()
72
73
74    def set_hexstr(self, hexstr):
75        h = hexstr.replace(':','')
76        self.buf= str().join([chr(int(h[i:i+2],16)) \
77                for i in range(0,2*fedid.HASHSIZE,2)])
78
79    def get_hexstr(self):
80        """Return the hexstring representation of the fedid"""
81        return self.__str__()
82
83    def set_bits(self, bits):
84        """Set the fedid to bits(a 160 bit buffer)"""
85        self.buf = bits
86
87    def get_bits(self):
88        """Get the 160 bit buffer from the fedid"""
89        return self.buf
90
91    def set_certstr(self, certstr):
92        """
93        Certstr is the contents of a certificate file.  Unfortunately we
94        basically have to stuff it back into a temp file to read it.
95        """
96        tf = tempfile.NamedTemporaryFile()
97        tf.write(certstr)
98        tf.flush()
99        self.set_file(tf.name)
100        tf.close()
101
102    def set_file(self, file):
103        """Get the fedid from a certificate file
104
105        Calculate the SHA1 hash over the bit string of the public key as
106        defined in RFC3280.
107        """
108        self.buf = None
109        if legacy: self.digest_bits(get_key_bits_from_file(file))
110        else: self.set_cert(X509.load_cert(file))
111
112    def set_cert(self, cert):
113        """Get the fedid from a certificate.
114
115        Calculate the SHA1 hash over the bit string of the public key as
116        defined in RFC3280.
117        """
118
119        self.buf = None
120        if (cert != None):
121            if legacy:
122                self.digest_bits(get_key_bits_from_cert(cert))
123            else:
124                b = []
125                k = cert.get_pubkey()
126
127                # Getting the key was easy, but getting the bit string of the
128                # key requires a side trip through ASN.1
129                dec = decoder.decode(k.as_der())
130
131                # kv is a tuple of the bits in the key.  The loop below
132                # recombines these into bytes and then into a buffer for the
133                # SSL digest function.
134                kv =  dec[0].getComponentByPosition(1)
135                for i in range(0, len(kv), 8):
136                    v = 0
137                    for j in range(0, 8):
138                        v = (v << 1) + kv[i+j]
139                    b.append(v)
140                # The comprehension turns b from a list of bytes into a buffer
141                # (string) of bytes
142                self.digest_bits(str().join([chr(x) for x in b]))
143
144def generate_fedid(subj, bits=2048, log=None, dir=None, trace=sys.stderr,
145        ssl_prog="/usr/bin/openssl"):
146    """
147    Create a new certificate and derive a fedid from it.
148
149    The fedid and the certificate are returned as a tuple.
150    """
151
152    keypath = None
153    certpath = None
154    try:
155        try:
156            kd, keypath = tempfile.mkstemp(dir=dir, prefix="key",
157                    suffix=".pem")
158            cd, certpath = tempfile.mkstemp(dir=dir, prefix="cert",
159                    suffix=".pem")
160
161            cmd = [ssl_prog, "req", "-text", "-newkey", 
162                    "rsa:%d" % bits, "-keyout", keypath,  "-nodes", 
163                    "-subj", "/CN=%s" % subj, "-x509", "-days", "365",
164                    "-out", certpath]
165
166            if log:
167                log.debug("[generate_fedid] %s" % " ".join(cmd))
168
169            if trace: call_out = trace
170            else: 
171                call_out = open("/dev/null", "w")
172
173            rv = subprocess.call(cmd, stdout=call_out, stderr=call_out)
174            if log: log.debug("rv = %d" % rv)
175            if rv == 0:
176                cert = ""
177                for p in (certpath, keypath):
178                    f = open(p)
179                    for line in f:
180                        cert += line
181               
182                fid = fedid(file=certpath)
183                return (fid, cert)
184            else:
185                return (None, None)
186        except EnvironmentError, e:
187            raise e
188    finally:
189        if keypath: os.remove(keypath)
190        if certpath: os.remove(certpath)
Note: See TracBrowser for help on using the repository browser.