re6st-conf 6.16 KB
Newer Older
1
#!/usr/bin/python
2 3
import argparse, atexit, binascii, errno, hashlib
import os, subprocess, sqlite3, sys, time
Guillaume Bury's avatar
Guillaume Bury committed
4
from OpenSSL import crypto
5
from re6st import registry, utils, x509
6

Julien Muchembled's avatar
Julien Muchembled committed
7 8
def create(path, text=None, mode=0666):
    fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
9 10 11 12
    try:
        os.write(fd, text)
    finally:
        os.close(fd)
Guillaume Bury's avatar
Guillaume Bury committed
13

14 15 16
def loadCert(pem):
    return crypto.load_certificate(crypto.FILETYPE_PEM, pem)

Guillaume Bury's avatar
Guillaume Bury committed
17 18
def main():
    parser = argparse.ArgumentParser(
Julien Muchembled's avatar
Julien Muchembled committed
19 20
        description="Setup script for re6stnet.",
        formatter_class=utils.HelpFormatter)
Guillaume Bury's avatar
Guillaume Bury committed
21
    _ = parser.add_argument
22 23
    _('--fingerprint', metavar='ALG:FINGERPRINT',
        help="Check CA fingerprint to protect against MITM.")
Julien Muchembled's avatar
Julien Muchembled committed
24 25
    _('--registry', required=True, metavar='URL',
        help="HTTP URL of the server delivering certificates.")
26 27 28 29
    _('--is-needed', action='store_true',
        help="Exit immediately after asking the registry CA. Status code is"
             " non-zero if we're already part of the network, which means"
             " re6st is already running or we're behind a re6st router.")
Guillaume Bury's avatar
Guillaume Bury committed
30
    _('--ca-only', action='store_true',
Julien Muchembled's avatar
Julien Muchembled committed
31 32 33 34 35 36 37 38 39
        help='Only fetch CA from registry and exit.')
    _('-d', '--dir',
        help="Directory where the key and certificate will be stored.")
    _('-r', '--req', nargs=2, action='append', metavar=('KEY', 'VALUE'),
        help="The registry only sets the Common Name of your certificate,"
             " which actually encodes your allocated prefix in the network."
             " You can repeat this option to add any field you want to its"
             " subject.")
    _('--token', help="The token you received.")
Guillaume Bury's avatar
Guillaume Bury committed
40
    config = parser.parse_args()
41 42
    if config.dir:
        os.chdir(config.dir)
43
    conf_path = 're6stnet.conf'
44 45 46
    ca_path = 'ca.crt'
    cert_path = 'cert.crt'
    key_path = 'cert.key'
Guillaume Bury's avatar
Guillaume Bury committed
47

Guillaume Bury's avatar
Guillaume Bury committed
48
    # Establish connection with server
49
    s = registry.RegistryClient(config.registry)
Guillaume Bury's avatar
Guillaume Bury committed
50 51

    # Get CA
52 53 54 55 56 57 58 59 60 61 62 63 64 65
    ca = loadCert(s.getCa())
    if config.fingerprint:
        try:
            alg, fingerprint = config.fingerprint.split(':', 1)
            fingerprint = binascii.a2b_hex(fingerprint)
            if hashlib.new(alg).digest_size != len(fingerprint):
                raise ValueError("wrong size")
        except StandardError, e:
            parser.error("invalid fingerprint: %s" % e)
        if x509.fingerprint(ca, alg).digest() != fingerprint:
            sys.exit("CA fingerprint doesn't match")
    else:
        print "WARNING: it is strongly recommended to use --fingerprint option."
    network = x509.networkFromCa(ca)
66 67 68 69 70 71
    if config.is_needed:
        route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
                                       utils.ipFromBin(network)),
                                      stdout=subprocess.PIPE).communicate()
        sys.exit(err or route and
            utils.binFromIp(route.split()[8]).startswith(network))
Guillaume Bury's avatar
Guillaume Bury committed
72

73
    create(ca_path, crypto.dump_certificate(crypto.FILETYPE_PEM, ca))
Guillaume Bury's avatar
Guillaume Bury committed
74
    if config.ca_only:
75
        sys.exit()
Guillaume Bury's avatar
Guillaume Bury committed
76

77
    reserved = 'CN', 'serial'
Guillaume Bury's avatar
Guillaume Bury committed
78
    req = crypto.X509Req()
79 80
    try:
        with open(cert_path) as f:
81
            cert = loadCert(f.read())
82
        components = dict(cert.get_subject().get_components())
83 84
        for k in reserved:
            components.pop(k, None)
85 86 87 88
    except IOError, e:
        if e.errno != errno.ENOENT:
            raise
        components = {}
Guillaume Bury's avatar
Guillaume Bury committed
89
    if config.req:
90 91 92
        components.update(config.req)
    subj = req.get_subject()
    for k, v in components.iteritems():
93 94
        if k in reserved:
            sys.exit(k + " field is reserved.")
95
        if v:
Julien Muchembled's avatar
Julien Muchembled committed
96
            setattr(subj, k, v)
Guillaume Bury's avatar
Guillaume Bury committed
97

Julien Muchembled's avatar
Julien Muchembled committed
98
    cert_fd = token_advice = None
99
    token = config.token
Julien Muchembled's avatar
Julien Muchembled committed
100
    try:
101
        if not token:
Julien Muchembled's avatar
Julien Muchembled committed
102
            token_advice = "Use --token to retry without asking a new token\n"
103 104
            while not token:
                token = raw_input('Please enter your token: ')
Guillaume Bury's avatar
Guillaume Bury committed
105

106 107 108 109 110 111 112 113
        try:
            with open(key_path) as f:
                pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
            key = None
            print "Reusing existing key."
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise
114 115
            bits = ca.get_pubkey().bits()
            print "Generating %s-bit key ..." % bits
116
            pkey = crypto.PKey()
117
            pkey.generate_key(crypto.TYPE_RSA, bits)
118 119
            key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
            create(key_path, key, 0600)
Guillaume Bury's avatar
Guillaume Bury committed
120

Julien Muchembled's avatar
Julien Muchembled committed
121
        req.set_pubkey(pkey)
122
        req.sign(pkey, 'sha512')
Julien Muchembled's avatar
Julien Muchembled committed
123 124
        req = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)

125 126 127
        # First make sure we can open certificate file for writing,
        # to avoid using our token for nothing.
        cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666)
Julien Muchembled's avatar
Julien Muchembled committed
128
        print "Requesting certificate ..."
129
        cert = s.requestCertificate(token, req)
Julien Muchembled's avatar
Julien Muchembled committed
130 131 132 133
        if not cert:
            token_advice = None
            sys.exit("Error: invalid or expired token")
    except:
134
        if cert_fd is not None and not os.lseek(cert_fd, 0, os.SEEK_END):
Julien Muchembled's avatar
Julien Muchembled committed
135 136 137 138 139
            os.remove(cert_path)
        if token_advice:
            atexit.register(sys.stdout.write, token_advice)
        raise
    os.write(cert_fd, cert)
140
    os.ftruncate(cert_fd, len(cert))
Julien Muchembled's avatar
Julien Muchembled committed
141
    os.close(cert_fd)
142

143
    cert = loadCert(cert)
144
    not_after = x509.notAfter(cert)
145
    print("Setup complete. Certificate is valid until %s UTC"
146 147 148
          " and will be automatically renewed after %s UTC.\n"
          "Do not forget to backup to your private key (%s) or"
          " you will lose your assigned subnet." % (
149
        time.asctime(time.gmtime(not_after)),
150 151
        time.asctime(time.gmtime(not_after - registry.RENEW_PERIOD)),
        key_path))
Guillaume Bury's avatar
Guillaume Bury committed
152

153 154 155 156 157 158 159 160 161 162 163 164 165
    if not os.path.lexists(conf_path):
        create(conf_path, """\
registry %s
ca %s
cert %s
key %s
# increase re6stnet verbosity:
#verbose 3
# enable OpenVPN logging:
#ovpnlog
# increase OpenVPN verbosity:
#O--verb
#O3
166
""" % (config.registry, ca_path, cert_path, key_path))
167 168
        print "Sample configuration file created."

169
    cn = x509.subnetFromCert(cert)
170
    subnet = network + utils.binFromSubnet(cn)
171 172
    print "Your subnet: %s/%u (CN=%s)" \
        % (utils.ipFromBin(subnet), len(subnet), cn)
173

Guillaume Bury's avatar
Guillaume Bury committed
174 175
if __name__ == "__main__":
    main()