/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2019-03-12 20:13:34 UTC
  • mto: This revision was merged to the branch mainline in revision 382.
  • Revision ID: teddy@recompile.se-20190312201334-my3htrprewjosuw5
mandos-ctl: Refactor

* mandos-ctl: Reorder everything into logical order; put main() first,
              and put every subsequent definition as soon as possible
              after its first use, except superclasses which need to
              be placed before the classes inheriting from them.
              Reorder all tests to match.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
#
13
13
# Everything else is
14
 
# Copyright © 2008-2016 Teddy Hogeborn
15
 
# Copyright © 2008-2016 Björn Påhlsson
16
 
#
17
 
# This program is free software: you can redistribute it and/or modify
18
 
# it under the terms of the GNU General Public License as published by
 
14
# Copyright © 2008-2019 Teddy Hogeborn
 
15
# Copyright © 2008-2019 Björn Påhlsson
 
16
#
 
17
# This file is part of Mandos.
 
18
#
 
19
# Mandos is free software: you can redistribute it and/or modify it
 
20
# under the terms of the GNU General Public License as published by
19
21
# the Free Software Foundation, either version 3 of the License, or
20
22
# (at your option) any later version.
21
23
#
22
 
#     This program is distributed in the hope that it will be useful,
23
 
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
 
24
#     Mandos is distributed in the hope that it will be useful, but
 
25
#     WITHOUT ANY WARRANTY; without even the implied warranty of
24
26
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
27
#     GNU General Public License for more details.
26
28
#
27
29
# You should have received a copy of the GNU General Public License
28
 
# along with this program.  If not, see
29
 
# <http://www.gnu.org/licenses/>.
 
30
# along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
30
31
#
31
32
# Contact the authors at <mandos@recompile.se>.
32
33
#
114
115
if sys.version_info.major == 2:
115
116
    str = unicode
116
117
 
117
 
version = "1.7.13"
 
118
version = "1.8.3"
118
119
stored_state_file = "clients.pickle"
119
120
 
120
121
logger = logging.getLogger()
495
496
class AvahiServiceToSyslog(AvahiService):
496
497
    def rename(self, *args, **kwargs):
497
498
        """Add the new name to the syslog messages"""
498
 
        ret = AvahiService.rename(self, *args, **kwargs)
 
499
        ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
499
500
        syslogger.setFormatter(logging.Formatter(
500
501
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
501
502
            .format(self.name)))
513
514
    _library = ctypes.cdll.LoadLibrary(library)
514
515
    del library
515
516
    _need_version = b"3.3.0"
 
517
    _tls_rawpk_version = b"3.6.6"
516
518
 
517
519
    def __init__(self):
518
520
        # Need to use "self" here, since this method is called before
529
531
    E_INTERRUPTED = -52
530
532
    E_AGAIN = -28
531
533
    CRT_OPENPGP = 2
 
534
    CRT_RAWPK = 3
532
535
    CLIENT = 2
533
536
    SHUT_RDWR = 0
534
537
    CRD_CERTIFICATE = 1
535
538
    E_NO_CERTIFICATE_FOUND = -49
 
539
    X509_FMT_DER = 0
 
540
    NO_TICKETS = 1<<10
 
541
    ENABLE_RAWPK = 1<<18
 
542
    CTYPE_PEERS = 3
 
543
    KEYID_USE_SHA256 = 1        # gnutls/x509.h
536
544
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
537
545
 
538
546
    # Types
592
600
    class ClientSession(object):
593
601
        def __init__(self, socket, credentials=None):
594
602
            self._c_object = gnutls.session_t()
595
 
            gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
 
603
            gnutls_flags = gnutls.CLIENT
 
604
            if gnutls.check_version("3.5.6"):
 
605
                gnutls_flags |= gnutls.NO_TICKETS
 
606
            if gnutls.has_rawpk:
 
607
                gnutls_flags |= gnutls.ENABLE_RAWPK
 
608
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
 
609
            del gnutls_flags
596
610
            gnutls.set_default_priority(self._c_object)
597
611
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
598
612
            gnutls.handshake_set_private_extensions(self._c_object,
730
744
    check_version.argtypes = [ctypes.c_char_p]
731
745
    check_version.restype = ctypes.c_char_p
732
746
 
733
 
    # All the function declarations below are from gnutls/openpgp.h
734
 
 
735
 
    openpgp_crt_init = _library.gnutls_openpgp_crt_init
736
 
    openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
737
 
    openpgp_crt_init.restype = _error_code
738
 
 
739
 
    openpgp_crt_import = _library.gnutls_openpgp_crt_import
740
 
    openpgp_crt_import.argtypes = [openpgp_crt_t,
741
 
                                   ctypes.POINTER(datum_t),
742
 
                                   openpgp_crt_fmt_t]
743
 
    openpgp_crt_import.restype = _error_code
744
 
 
745
 
    openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
746
 
    openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
747
 
                                        ctypes.POINTER(ctypes.c_uint)]
748
 
    openpgp_crt_verify_self.restype = _error_code
749
 
 
750
 
    openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
751
 
    openpgp_crt_deinit.argtypes = [openpgp_crt_t]
752
 
    openpgp_crt_deinit.restype = None
753
 
 
754
 
    openpgp_crt_get_fingerprint = (
755
 
        _library.gnutls_openpgp_crt_get_fingerprint)
756
 
    openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
757
 
                                            ctypes.c_void_p,
758
 
                                            ctypes.POINTER(
759
 
                                                ctypes.c_size_t)]
760
 
    openpgp_crt_get_fingerprint.restype = _error_code
 
747
    has_rawpk = bool(check_version(_tls_rawpk_version))
 
748
 
 
749
    if has_rawpk:
 
750
        # Types
 
751
        class pubkey_st(ctypes.Structure):
 
752
            _fields = []
 
753
        pubkey_t = ctypes.POINTER(pubkey_st)
 
754
 
 
755
        x509_crt_fmt_t = ctypes.c_int
 
756
 
 
757
        # All the function declarations below are from gnutls/abstract.h
 
758
        pubkey_init = _library.gnutls_pubkey_init
 
759
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
 
760
        pubkey_init.restype = _error_code
 
761
 
 
762
        pubkey_import = _library.gnutls_pubkey_import
 
763
        pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
 
764
                                  x509_crt_fmt_t]
 
765
        pubkey_import.restype = _error_code
 
766
 
 
767
        pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
 
768
        pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
 
769
                                      ctypes.POINTER(ctypes.c_ubyte),
 
770
                                      ctypes.POINTER(ctypes.c_size_t)]
 
771
        pubkey_get_key_id.restype = _error_code
 
772
 
 
773
        pubkey_deinit = _library.gnutls_pubkey_deinit
 
774
        pubkey_deinit.argtypes = [pubkey_t]
 
775
        pubkey_deinit.restype = None
 
776
    else:
 
777
        # All the function declarations below are from gnutls/openpgp.h
 
778
 
 
779
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
 
780
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
 
781
        openpgp_crt_init.restype = _error_code
 
782
 
 
783
        openpgp_crt_import = _library.gnutls_openpgp_crt_import
 
784
        openpgp_crt_import.argtypes = [openpgp_crt_t,
 
785
                                       ctypes.POINTER(datum_t),
 
786
                                       openpgp_crt_fmt_t]
 
787
        openpgp_crt_import.restype = _error_code
 
788
 
 
789
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
 
790
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
 
791
                                            ctypes.POINTER(ctypes.c_uint)]
 
792
        openpgp_crt_verify_self.restype = _error_code
 
793
 
 
794
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
 
795
        openpgp_crt_deinit.argtypes = [openpgp_crt_t]
 
796
        openpgp_crt_deinit.restype = None
 
797
 
 
798
        openpgp_crt_get_fingerprint = (
 
799
            _library.gnutls_openpgp_crt_get_fingerprint)
 
800
        openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
 
801
                                                ctypes.c_void_p,
 
802
                                                ctypes.POINTER(
 
803
                                                    ctypes.c_size_t)]
 
804
        openpgp_crt_get_fingerprint.restype = _error_code
 
805
 
 
806
    if check_version("3.6.4"):
 
807
        certificate_type_get2 = _library.gnutls_certificate_type_get2
 
808
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
 
809
        certificate_type_get2.restype = _error_code
761
810
 
762
811
    # Remove non-public functions
763
812
    del _error_code, _retry_on_error
799
848
    disable_initiator_tag: a GLib event source tag, or None
800
849
    enabled:    bool()
801
850
    fingerprint: string (40 or 32 hexadecimal digits); used to
802
 
                 uniquely identify the client
 
851
                 uniquely identify an OpenPGP client
 
852
    key_id: string (64 hexadecimal digits); used to uniquely identify
 
853
            a client using raw public keys
803
854
    host:       string; available for use by the checker command
804
855
    interval:   datetime.timedelta(); How often to start a new checker
805
856
    last_approval_request: datetime.datetime(); (UTC) or None
823
874
    """
824
875
 
825
876
    runtime_expansions = ("approval_delay", "approval_duration",
826
 
                          "created", "enabled", "expires",
 
877
                          "created", "enabled", "expires", "key_id",
827
878
                          "fingerprint", "host", "interval",
828
879
                          "last_approval_request", "last_checked_ok",
829
880
                          "last_enabled", "name", "timeout")
859
910
            client["enabled"] = config.getboolean(client_name,
860
911
                                                  "enabled")
861
912
 
862
 
            # Uppercase and remove spaces from fingerprint for later
863
 
            # comparison purposes with return value from the
864
 
            # fingerprint() function
 
913
            # Uppercase and remove spaces from key_id and fingerprint
 
914
            # for later comparison purposes with return value from the
 
915
            # key_id() and fingerprint() functions
 
916
            client["key_id"] = (section.get("key_id", "").upper()
 
917
                                .replace(" ", ""))
865
918
            client["fingerprint"] = (section["fingerprint"].upper()
866
919
                                     .replace(" ", ""))
867
920
            if "secret" in section:
911
964
            self.expires = None
912
965
 
913
966
        logger.debug("Creating client %r", self.name)
 
967
        logger.debug("  Key ID: %s", self.key_id)
914
968
        logger.debug("  Fingerprint: %s", self.fingerprint)
915
969
        self.created = settings.get("created",
916
970
                                    datetime.datetime.utcnow())
1460
1514
                         exc_info=error)
1461
1515
        return xmlstring
1462
1516
 
 
1517
 
1463
1518
try:
1464
1519
    dbus.OBJECT_MANAGER_IFACE
1465
1520
except AttributeError:
1997
2052
    def Name_dbus_property(self):
1998
2053
        return dbus.String(self.name)
1999
2054
 
 
2055
    # KeyID - property
 
2056
    @dbus_annotations(
 
2057
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
 
2058
    @dbus_service_property(_interface, signature="s", access="read")
 
2059
    def KeyID_dbus_property(self):
 
2060
        return dbus.String(self.key_id)
 
2061
 
2000
2062
    # Fingerprint - property
2001
2063
    @dbus_annotations(
2002
2064
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
2158
2220
 
2159
2221
 
2160
2222
class ProxyClient(object):
2161
 
    def __init__(self, child_pipe, fpr, address):
 
2223
    def __init__(self, child_pipe, key_id, fpr, address):
2162
2224
        self._pipe = child_pipe
2163
 
        self._pipe.send(('init', fpr, address))
 
2225
        self._pipe.send(('init', key_id, fpr, address))
2164
2226
        if not self._pipe.recv():
2165
 
            raise KeyError(fpr)
 
2227
            raise KeyError(key_id or fpr)
2166
2228
 
2167
2229
    def __getattribute__(self, name):
2168
2230
        if name == '_pipe':
2235
2297
 
2236
2298
            approval_required = False
2237
2299
            try:
2238
 
                try:
2239
 
                    fpr = self.fingerprint(
2240
 
                        self.peer_certificate(session))
2241
 
                except (TypeError, gnutls.Error) as error:
2242
 
                    logger.warning("Bad certificate: %s", error)
2243
 
                    return
2244
 
                logger.debug("Fingerprint: %s", fpr)
2245
 
 
2246
 
                try:
2247
 
                    client = ProxyClient(child_pipe, fpr,
 
2300
                if gnutls.has_rawpk:
 
2301
                    fpr = ""
 
2302
                    try:
 
2303
                        key_id = self.key_id(
 
2304
                            self.peer_certificate(session))
 
2305
                    except (TypeError, gnutls.Error) as error:
 
2306
                        logger.warning("Bad certificate: %s", error)
 
2307
                        return
 
2308
                    logger.debug("Key ID: %s", key_id)
 
2309
 
 
2310
                else:
 
2311
                    key_id = ""
 
2312
                    try:
 
2313
                        fpr = self.fingerprint(
 
2314
                            self.peer_certificate(session))
 
2315
                    except (TypeError, gnutls.Error) as error:
 
2316
                        logger.warning("Bad certificate: %s", error)
 
2317
                        return
 
2318
                    logger.debug("Fingerprint: %s", fpr)
 
2319
 
 
2320
                try:
 
2321
                    client = ProxyClient(child_pipe, key_id, fpr,
2248
2322
                                         self.client_address)
2249
2323
                except KeyError:
2250
2324
                    return
2327
2401
 
2328
2402
    @staticmethod
2329
2403
    def peer_certificate(session):
2330
 
        "Return the peer's OpenPGP certificate as a bytestring"
2331
 
        # If not an OpenPGP certificate...
2332
 
        if (gnutls.certificate_type_get(session._c_object)
2333
 
            != gnutls.CRT_OPENPGP):
 
2404
        "Return the peer's certificate as a bytestring"
 
2405
        try:
 
2406
            cert_type = gnutls.certificate_type_get2(session._c_object,
 
2407
                                                     gnutls.CTYPE_PEERS)
 
2408
        except AttributeError:
 
2409
            cert_type = gnutls.certificate_type_get(session._c_object)
 
2410
        if gnutls.has_rawpk:
 
2411
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
 
2412
        else:
 
2413
            valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
 
2414
        # If not a valid certificate type...
 
2415
        if cert_type not in valid_cert_types:
 
2416
            logger.info("Cert type %r not in %r", cert_type,
 
2417
                        valid_cert_types)
2334
2418
            # ...return invalid data
2335
2419
            return b""
2336
2420
        list_size = ctypes.c_uint(1)
2344
2428
        return ctypes.string_at(cert.data, cert.size)
2345
2429
 
2346
2430
    @staticmethod
 
2431
    def key_id(certificate):
 
2432
        "Convert a certificate bytestring to a hexdigit key ID"
 
2433
        # New GnuTLS "datum" with the public key
 
2434
        datum = gnutls.datum_t(
 
2435
            ctypes.cast(ctypes.c_char_p(certificate),
 
2436
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2437
            ctypes.c_uint(len(certificate)))
 
2438
        # XXX all these need to be created in the gnutls "module"
 
2439
        # New empty GnuTLS certificate
 
2440
        pubkey = gnutls.pubkey_t()
 
2441
        gnutls.pubkey_init(ctypes.byref(pubkey))
 
2442
        # Import the raw public key into the certificate
 
2443
        gnutls.pubkey_import(pubkey,
 
2444
                             ctypes.byref(datum),
 
2445
                             gnutls.X509_FMT_DER)
 
2446
        # New buffer for the key ID
 
2447
        buf = ctypes.create_string_buffer(32)
 
2448
        buf_len = ctypes.c_size_t(len(buf))
 
2449
        # Get the key ID from the raw public key into the buffer
 
2450
        gnutls.pubkey_get_key_id(pubkey,
 
2451
                                 gnutls.KEYID_USE_SHA256,
 
2452
                                 ctypes.cast(ctypes.byref(buf),
 
2453
                                             ctypes.POINTER(ctypes.c_ubyte)),
 
2454
                                 ctypes.byref(buf_len))
 
2455
        # Deinit the certificate
 
2456
        gnutls.pubkey_deinit(pubkey)
 
2457
 
 
2458
        # Convert the buffer to a Python bytestring
 
2459
        key_id = ctypes.string_at(buf, buf_len.value)
 
2460
        # Convert the bytestring to hexadecimal notation
 
2461
        hex_key_id = binascii.hexlify(key_id).upper()
 
2462
        return hex_key_id
 
2463
 
 
2464
    @staticmethod
2347
2465
    def fingerprint(openpgp):
2348
2466
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
2349
2467
        # New GnuTLS "datum" with the OpenPGP public key
2363
2481
                                       ctypes.byref(crtverify))
2364
2482
        if crtverify.value != 0:
2365
2483
            gnutls.openpgp_crt_deinit(crt)
2366
 
            raise gnutls.CertificateSecurityError("Verify failed")
 
2484
            raise gnutls.CertificateSecurityError(code
 
2485
                                                  =crtverify.value)
2367
2486
        # New buffer for the fingerprint
2368
2487
        buf = ctypes.create_string_buffer(20)
2369
2488
        buf_len = ctypes.c_size_t()
2576
2695
        command = request[0]
2577
2696
 
2578
2697
        if command == 'init':
2579
 
            fpr = request[1]
2580
 
            address = request[2]
 
2698
            key_id = request[1].decode("ascii")
 
2699
            fpr = request[2].decode("ascii")
 
2700
            address = request[3]
2581
2701
 
2582
2702
            for c in self.clients.values():
2583
 
                if c.fingerprint == fpr:
 
2703
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
 
2704
                    continue
 
2705
                if key_id and c.key_id == key_id:
 
2706
                    client = c
 
2707
                    break
 
2708
                if fpr and c.fingerprint == fpr:
2584
2709
                    client = c
2585
2710
                    break
2586
2711
            else:
2587
 
                logger.info("Client not found for fingerprint: %s, ad"
2588
 
                            "dress: %s", fpr, address)
 
2712
                logger.info("Client not found for key ID: %s, address"
 
2713
                            ": %s", key_id or fpr, address)
2589
2714
                if self.use_dbus:
2590
2715
                    # Emit D-Bus signal
2591
 
                    mandos_dbus_service.ClientNotFound(fpr,
 
2716
                    mandos_dbus_service.ClientNotFound(key_id or fpr,
2592
2717
                                                       address[0])
2593
2718
                parent_pipe.send(False)
2594
2719
                return False
2857
2982
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2858
2983
 
2859
2984
    # Default values for config file for server-global settings
 
2985
    if gnutls.has_rawpk:
 
2986
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
 
2987
                    ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
 
2988
    else:
 
2989
        priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2990
                    ":+SIGN-DSA-SHA256")
2860
2991
    server_defaults = {"interface": "",
2861
2992
                       "address": "",
2862
2993
                       "port": "",
2863
2994
                       "debug": "False",
2864
 
                       "priority":
2865
 
                       "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2866
 
                       ":+SIGN-DSA-SHA256",
 
2995
                       "priority": priority,
2867
2996
                       "servicename": "Mandos",
2868
2997
                       "use_dbus": "True",
2869
2998
                       "use_ipv6": "True",
2874
3003
                       "foreground": "False",
2875
3004
                       "zeroconf": "True",
2876
3005
                       }
 
3006
    del priority
2877
3007
 
2878
3008
    # Parse config file for server-global settings
2879
3009
    server_config = configparser.SafeConfigParser(server_defaults)
2882
3012
    # Convert the SafeConfigParser object to a dict
2883
3013
    server_settings = server_config.defaults()
2884
3014
    # Use the appropriate methods on the non-string config options
2885
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
3015
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
 
3016
                   "foreground", "zeroconf"):
2886
3017
        server_settings[option] = server_config.getboolean("DEFAULT",
2887
3018
                                                           option)
2888
3019
    if server_settings["port"]:
3122
3253
                        for k in ("name", "host"):
3123
3254
                            if isinstance(value[k], bytes):
3124
3255
                                value[k] = value[k].decode("utf-8")
 
3256
                        if not value.has_key("key_id"):
 
3257
                            value["key_id"] = ""
 
3258
                        elif not value.has_key("fingerprint"):
 
3259
                            value["fingerprint"] = ""
3125
3260
                    #  old_client_settings
3126
3261
                    # .keys()
3127
3262
                    old_client_settings = {
3264
3399
                pass
3265
3400
 
3266
3401
            @dbus.service.signal(_interface, signature="ss")
3267
 
            def ClientNotFound(self, fingerprint, address):
 
3402
            def ClientNotFound(self, key_id, address):
3268
3403
                "D-Bus signal"
3269
3404
                pass
3270
3405