tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

makedesc.py (12337B)


      1 #!/usr/bin/env python
      2 # Copyright 2014-2019, The Tor Project, Inc.
      3 # See LICENSE for license information
      4 
      5 # This is a kludgey python script that uses ctypes and openssl to sign
      6 # router descriptors and extrainfo documents and put all the keys in
      7 # the right places.  There are examples at the end of the file.
      8 
      9 # I've used this to make inputs for unit tests.  I wouldn't suggest
     10 # using it for anything else.
     11 
     12 # Future imports for Python 2.7, mandatory in 3.0
     13 from __future__ import division
     14 from __future__ import print_function
     15 from __future__ import unicode_literals
     16 
     17 import base64
     18 import binascii
     19 import ctypes
     20 import ctypes.util
     21 import hashlib
     22 import optparse
     23 import os
     24 import re
     25 import struct
     26 import time
     27 
     28 import slow_ed25519
     29 import slownacl_curve25519
     30 import ed25519_exts_ref
     31 
     32 try:
     33    xrange  # Python 2
     34 except NameError:
     35    xrange = range  # Python 3
     36 
     37 # Pull in the openssl stuff we need.
     38 
     39 crypt = ctypes.CDLL(ctypes.util.find_library('crypto'))
     40 BIO_s_mem = crypt.BIO_s_mem
     41 BIO_s_mem.argtypes = []
     42 BIO_s_mem.restype = ctypes.c_void_p
     43 
     44 BIO_new = crypt.BIO_new
     45 BIO_new.argtypes = [ctypes.c_void_p]
     46 BIO_new.restype = ctypes.c_void_p
     47 
     48 crypt.BIO_free.argtypes = [ctypes.c_void_p]
     49 crypt.BIO_free.restype = ctypes.c_int
     50 
     51 crypt.BIO_ctrl.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_long, ctypes.c_void_p ]
     52 crypt.BIO_ctrl.restype = ctypes.c_long
     53 
     54 crypt.PEM_write_bio_RSAPublicKey.argtypes = [ ctypes.c_void_p, ctypes.c_void_p ]
     55 crypt.PEM_write_bio_RSAPublicKey.restype = ctypes.c_int
     56 
     57 RSA_generate_key = crypt.RSA_generate_key
     58 RSA_generate_key.argtypes = [ctypes.c_int, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_void_p]
     59 RSA_generate_key.restype = ctypes.c_void_p
     60 
     61 RSA_private_encrypt = crypt.RSA_private_encrypt
     62 RSA_private_encrypt.argtypes = [
     63    ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int ]
     64 RSA_private_encrypt.restype = ctypes.c_int
     65 
     66 i2d_RSAPublicKey = crypt.i2d_RSAPublicKey
     67 i2d_RSAPublicKey.argtypes = [
     68    ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p)
     69 ]
     70 i2d_RSAPublicKey.restype = ctypes.c_int
     71 
     72 
     73 HEADER = """\
     74 router fred 127.0.0.1 9001 0 9002
     75 identity-ed25519
     76 {d.ED_CERT}
     77 signing-key
     78 {d.RSA_IDENTITY}
     79 master-key-ed25519 {d.ED_IDENTITY}
     80 onion-key
     81 {d.RSA_ONION_KEY}
     82 ntor-onion-key {d.NTOR_ONION_KEY}
     83 ntor-onion-key-crosscert {d.NTOR_CROSSCERT_SIGN}
     84 {d.NTOR_CROSSCERT}
     85 onion-key-crosscert
     86 {d.RSA_CROSSCERT_ED}
     87 """
     88 
     89 FOOTER="""
     90 
     91 """
     92 
     93 def rsa_sign(msg, rsa):
     94    buf = ctypes.create_string_buffer(2048)
     95    n = RSA_private_encrypt(len(msg), msg, buf, rsa, 1)
     96    if n <= 0:
     97        raise Exception()
     98    return buf.raw[:n]
     99 
    100 def b64(x1):
    101    x = binascii.b2a_base64(x1)
    102    res = []
    103    for i in xrange(0, len(x), 64):
    104        res.append((x[i:i+64]).decode("ascii"))
    105    return "\n".join(res)
    106 
    107 def bio_extract(bio):
    108    buf = ctypes.c_char_p()
    109    length = crypt.BIO_ctrl(bio, 3, 0, ctypes.byref(buf))
    110    return ctypes.string_at(buf, length)
    111 
    112 def make_rsa_key(e=65537):
    113    rsa = crypt.RSA_generate_key(1024, e, None, None)
    114    bio = BIO_new(BIO_s_mem())
    115    crypt.PEM_write_bio_RSAPublicKey(bio, rsa)
    116    pem = bio_extract(bio).rstrip()
    117    crypt.BIO_free(bio)
    118    buf = ctypes.create_string_buffer(1024)
    119    pBuf = ctypes.c_char_p(ctypes.addressof(buf))
    120    n = crypt.i2d_RSAPublicKey(rsa, ctypes.byref(pBuf))
    121    s = buf.raw[:n]
    122    digest = hashlib.sha1(s).digest()
    123    pem = pem.decode("ascii")
    124    return (rsa,pem,digest)
    125 
    126 def makeEdSigningKeyCert(sk_master, pk_master, pk_signing, date,
    127                         includeSigning=False, certType=1):
    128    assert len(pk_signing) == len(pk_master) == 32
    129    expiration = struct.pack(b"!L", date//3600)
    130    if includeSigning:
    131        extensions = b"\x01\x00\x20\x04\x00%s"%(pk_master)
    132    else:
    133        extensions = b"\x00"
    134    signed = b"\x01%s%s\x01%s%s" % (
    135        bytes([certType]), expiration, pk_signing, extensions)
    136    signature = ed25519_exts_ref.signatureWithESK(signed, sk_master, pk_master)
    137    assert len(signature) == 64
    138    return signed+signature
    139 
    140 def objwrap(identifier, body):
    141    return ("-----BEGIN {0}-----\n"
    142            "{1}"
    143            "-----END {0}-----").format(identifier, body)
    144 
    145 MAGIC1 = "<<<<<<MAGIC>>>>>>"
    146 MAGIC2 = "<<<<<!#!#!#XYZZY#!#!#!>>>>>"
    147 
    148 class OnDemandKeys(object):
    149    def __init__(self, certDate=None):
    150        if certDate is None:
    151            certDate = int(time.time()) + 86400
    152        self.certDate = certDate
    153        self.rsa_id = None
    154        self.rsa_onion_key = None
    155        self.ed_id_sk = None
    156        self.ntor_sk = None
    157        self.ntor_crosscert = None
    158        self.rsa_crosscert_ed = None
    159        self.rsa_crosscert_noed = None
    160 
    161    @property
    162    def RSA_IDENTITY(self):
    163        if self.rsa_id is None:
    164            self.rsa_id, self.rsa_ident_pem, self.rsa_id_digest = make_rsa_key()
    165 
    166        return self.rsa_ident_pem
    167 
    168    @property
    169    def RSA_ID_DIGEST(self):
    170        self.RSA_IDENTITY
    171        return self.rsa_id_digest
    172 
    173    @property
    174    def RSA_FINGERPRINT_NOSPACE(self):
    175        return binascii.b2a_hex(self.RSA_ID_DIGEST).upper().decode("ascii")
    176 
    177    @property
    178    def RSA_ONION_KEY(self):
    179        if self.rsa_onion_key is None:
    180            self.rsa_onion_key, self.rsa_onion_pem, _ = make_rsa_key()
    181 
    182        return self.rsa_onion_pem
    183 
    184    @property
    185    def RSA_FINGERPRINT(self):
    186        hexdigest = self.RSA_FINGERPRINT_NOSPACE
    187        return " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4))
    188 
    189    @property
    190    def RSA_SIGNATURE(self):
    191        return MAGIC1
    192 
    193    @property
    194    def ED_SIGNATURE(self):
    195        return MAGIC2
    196 
    197    @property
    198    def NTOR_ONION_KEY(self):
    199        if self.ntor_sk is None:
    200            self.ntor_sk = slownacl_curve25519.Private()
    201            self.ntor_pk = self.ntor_sk.get_public()
    202        return base64.b64encode(self.ntor_pk.serialize()).decode("ascii")
    203 
    204    @property
    205    def ED_CERT(self):
    206        if self.ed_id_sk is None:
    207            self.ed_id_sk = ed25519_exts_ref.expandSK(os.urandom(32))
    208            self.ed_signing_sk = ed25519_exts_ref.expandSK(os.urandom(32))
    209            self.ed_id_pk = ed25519_exts_ref.publickeyFromESK(self.ed_id_sk)
    210            self.ed_signing_pk = ed25519_exts_ref.publickeyFromESK(self.ed_signing_sk)
    211            self.ed_cert = makeEdSigningKeyCert(self.ed_id_sk, self.ed_id_pk, self.ed_signing_pk, self.certDate, includeSigning=True, certType=4)
    212 
    213        return objwrap('ED25519 CERT', b64(self.ed_cert))
    214 
    215    @property
    216    def ED_IDENTITY(self):
    217        self.ED_CERT
    218        return binascii.b2a_base64(self.ed_id_pk).strip().decode("ascii")
    219 
    220    @property
    221    def NTOR_CROSSCERT(self):
    222        if self.ntor_crosscert is None:
    223            self.ED_CERT
    224            self.NTOR_ONION_KEY
    225 
    226            ed_privkey = self.ntor_sk.serialize() + os.urandom(32)
    227            ed_pub0 = ed25519_exts_ref.publickeyFromESK(ed_privkey)
    228            sign = ((ed_pub0[31]) & 255) >> 7
    229 
    230            self.ntor_crosscert = makeEdSigningKeyCert(self.ntor_sk.serialize() + os.urandom(32), ed_pub0, self.ed_id_pk, self.certDate, certType=10)
    231            self.ntor_crosscert_sign = sign
    232 
    233        return objwrap('ED25519 CERT', b64(self.ntor_crosscert))
    234 
    235    @property
    236    def NTOR_CROSSCERT_SIGN(self):
    237        self.NTOR_CROSSCERT
    238        return self.ntor_crosscert_sign
    239 
    240    @property
    241    def RSA_CROSSCERT_NOED(self):
    242        if self.rsa_crosscert_noed is None:
    243            self.RSA_ONION_KEY
    244            signed = self.RSA_ID_DIGEST
    245            self.rsa_crosscert_noed = rsa_sign(signed, self.rsa_onion_key)
    246        return objwrap("CROSSCERT",b64(self.rsa_crosscert_noed))
    247 
    248    @property
    249    def RSA_CROSSCERT_ED(self):
    250        if self.rsa_crosscert_ed is None:
    251            self.RSA_ONION_KEY
    252            self.ED_CERT
    253            signed = self.RSA_ID_DIGEST + self.ed_id_pk
    254            self.rsa_crosscert_ed = rsa_sign(signed, self.rsa_onion_key)
    255        return objwrap("CROSSCERT",b64(self.rsa_crosscert_ed))
    256 
    257    def sign_desc(self, body):
    258        idx = body.rfind("\nrouter-sig-ed25519 ")
    259        if idx >= 0:
    260            self.ED_CERT
    261            signed_part = body[:idx+len("\nrouter-sig-ed25519 ")]
    262            signed_part = "Tor router descriptor signature v1" + signed_part
    263            digest = hashlib.sha256(signed_part.encode("utf-8")).digest()
    264            ed_sig = ed25519_exts_ref.signatureWithESK(digest,
    265                                      self.ed_signing_sk, self.ed_signing_pk)
    266 
    267            body = body.replace(MAGIC2, base64.b64encode(ed_sig).decode("ascii").replace("=",""))
    268 
    269        self.RSA_IDENTITY
    270        idx = body.rindex("\nrouter-signature")
    271        end_of_sig = body.index("\n", idx+1)
    272 
    273        signed_part = body[:end_of_sig+1]
    274 
    275        digest = hashlib.sha1(signed_part.encode("utf-8")).digest()
    276        assert len(digest) == 20
    277 
    278        rsasig = rsa_sign(digest, self.rsa_id)
    279 
    280        body = body.replace(MAGIC1, objwrap("SIGNATURE", b64(rsasig)))
    281 
    282        return body
    283 
    284 
    285 def signdesc(body, args_out=None):
    286    rsa, ident_pem, id_digest = make_rsa_key()
    287    _, onion_pem, _ = make_rsa_key()
    288 
    289    need_ed = '{ED25519-CERT}' in body or '{ED25519-SIGNATURE}' in body
    290    if need_ed:
    291        sk_master = os.urandom(32)
    292        sk_signing = os.urandom(32)
    293        pk_master = slow_ed25519.pubkey(sk_master)
    294        pk_signing = slow_ed25519.pubkey(sk_signing)
    295 
    296    hexdigest = binascii.b2a_hex(id_digest).upper()
    297    fingerprint = " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4))
    298 
    299    MAGIC = "<<<<<<MAGIC>>>>>>"
    300    MORE_MAGIC = "<<<<<!#!#!#XYZZY#!#!#!>>>>>"
    301    args = {
    302        "RSA-IDENTITY" : ident_pem,
    303        "ONION-KEY" : onion_pem,
    304        "FINGERPRINT" : fingerprint,
    305        "FINGERPRINT-NOSPACE" : hexdigest,
    306        "RSA-SIGNATURE" : MAGIC
    307    }
    308    if need_ed:
    309        args['ED25519-CERT'] = makeEdSigningKeyCert(
    310            sk_master, pk_master, pk_signing)
    311        args['ED25519-SIGNATURE'] = MORE_MAGIC
    312 
    313    if args_out:
    314        args_out.update(args)
    315    body = body.format(**args)
    316 
    317    idx = body.rindex("\nrouter-signature")
    318    end_of_sig = body.index("\n", idx+1)
    319 
    320    signed_part = body[:end_of_sig+1]
    321 
    322    digest = hashlib.sha1(signed_part).digest()
    323    assert len(digest) == 20
    324 
    325    buf = ctypes.create_string_buffer(1024)
    326    n = RSA_private_encrypt(20, digest, buf, rsa, 1)
    327    sig = buf.raw[:n]
    328 
    329    sig = """-----BEGIN SIGNATURE-----
    330 %s
    331 -----END SIGNATURE-----""" % b64(sig).rstrip()
    332    body = body.replace(MAGIC, sig)
    333 
    334    return body.rstrip()
    335 
    336 def print_c_string(ident, body):
    337    print("static const char %s[] =" % ident)
    338    for line in body.split("\n"):
    339        print('  "%s\\n"' %(line))
    340    print("  ;")
    341 
    342 def emit_ri(name, body):
    343    info = OnDemandKeys()
    344    body = body.format(d=info)
    345    body = info.sign_desc(body)
    346    print_c_string("EX_RI_%s"%name.upper(), body)
    347 
    348 def emit_ei(name, body, fields):
    349    info = OnDemandKeys()
    350    body = body.format(d=info)
    351    body = info.sign_desc(body)
    352    print_c_string("EX_EI_%s"%name.upper(), body)
    353 
    354    print('ATTR_UNUSED static const char EX_EI_{NAME}_FP[] = "{d.RSA_FINGERPRINT_NOSPACE}";'.format(
    355        d=info, NAME=name.upper()))
    356    print("ATTR_UNUSED")
    357    print_c_string("EX_EI_%s_KEY"%name.upper(), info.RSA_IDENTITY)
    358 
    359 def analyze(s):
    360    while s:
    361        fields = {}
    362        s_pre = s
    363        while s.startswith(":::"):
    364            first,s=s.split("\n", 1)
    365            m = re.match(r'^:::(\w+)=(.*)',first)
    366            if not m:
    367                raise ValueError(first)
    368            k,v = m.groups()
    369            fields[k] = v
    370        if "name" not in fields:
    371            print(repr(s_pre))
    372 
    373        idx = s.find(":::")
    374        if idx != -1:
    375            body = s[:idx].rstrip()
    376            s = s[idx:]
    377        else:
    378            body = s.rstrip()
    379            s = ""
    380 
    381        yield (fields, body)
    382 
    383 def emit_entry(fields, s):
    384    try:
    385        name = fields['name']
    386        tp = fields['type']
    387    except KeyError:
    388        raise ValueError("missing required field")
    389 
    390    if tp == 'ei':
    391        emit_ei(name, s, fields)
    392    elif tp == 'ri':
    393        emit_ri(name, s)
    394    else:
    395        raise ValueError("unrecognized type")
    396 
    397 def process_file(s):
    398    print("""\
    399 /* These entries are automatically generated by makedesc.py to make sure
    400 * that their keys and signatures are right except when otherwise
    401 * specified. */
    402 """)
    403    for (fields, s) in analyze(s):
    404        emit_entry(fields, s)
    405 
    406 if __name__ == '__main__':
    407    import sys
    408    for fn in sys.argv[1:]:
    409        process_file(open(fn).read())