/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2019-07-29 16:35:53 UTC
  • Revision ID: teddy@recompile.se-20190729163553-1i442i2cbx64c537
Make tests and man page examples match

Make the tests test_manual_page_example[1-5] match exactly what is
written in the manual page, and add comments to manual page as
reminders to keep tests and manual page examples in sync.

* mandos-ctl (Test_commands_from_options.test_manual_page_example_1):
  Remove "--verbose" option, since the manual does not have it as the
  first example, and change assertion to match.
* mandos-ctl.xml (EXAMPLE): Add comments to all examples documenting
  which test function they correspond to.  Also remove unnecessary
  quotes from option arguments in fourth example, and clarify language
  slightly in fifth example.

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-2018 Teddy Hogeborn
15
 
# Copyright © 2008-2018 Björn Påhlsson
 
14
# Copyright © 2008-2019 Teddy Hogeborn
 
15
# Copyright © 2008-2019 Björn Påhlsson
16
16
#
17
17
# This file is part of Mandos.
18
18
#
115
115
if sys.version_info.major == 2:
116
116
    str = unicode
117
117
 
118
 
version = "1.7.19"
 
118
version = "1.8.4"
119
119
stored_state_file = "clients.pickle"
120
120
 
121
121
logger = logging.getLogger()
275
275
 
276
276
 
277
277
# Pretend that we have an Avahi module
278
 
class Avahi(object):
279
 
    """This isn't so much a class as it is a module-like namespace.
280
 
    It is instantiated once, and simulates having an Avahi module."""
 
278
class avahi(object):
 
279
    """This isn't so much a class as it is a module-like namespace."""
281
280
    IF_UNSPEC = -1               # avahi-common/address.h
282
281
    PROTO_UNSPEC = -1            # avahi-common/address.h
283
282
    PROTO_INET = 0               # avahi-common/address.h
287
286
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
288
287
    DBUS_PATH_SERVER = "/"
289
288
 
290
 
    def string_array_to_txt_array(self, t):
 
289
    @staticmethod
 
290
    def string_array_to_txt_array(t):
291
291
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
292
292
                           for s in t), signature="ay")
293
293
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
298
298
    SERVER_RUNNING = 2           # avahi-common/defs.h
299
299
    SERVER_COLLISION = 3         # avahi-common/defs.h
300
300
    SERVER_FAILURE = 4           # avahi-common/defs.h
301
 
avahi = Avahi()
302
301
 
303
302
 
304
303
class AvahiError(Exception):
504
503
 
505
504
 
506
505
# Pretend that we have a GnuTLS module
507
 
class GnuTLS(object):
508
 
    """This isn't so much a class as it is a module-like namespace.
509
 
    It is instantiated once, and simulates having a GnuTLS module."""
 
506
class gnutls(object):
 
507
    """This isn't so much a class as it is a module-like namespace."""
510
508
 
511
509
    library = ctypes.util.find_library("gnutls")
512
510
    if library is None:
513
511
        library = ctypes.util.find_library("gnutls-deb0")
514
512
    _library = ctypes.cdll.LoadLibrary(library)
515
513
    del library
516
 
    _need_version = b"3.3.0"
517
 
 
518
 
    def __init__(self):
519
 
        # Need to use "self" here, since this method is called before
520
 
        # the assignment to the "gnutls" global variable happens.
521
 
        if self.check_version(self._need_version) is None:
522
 
            raise self.Error("Needs GnuTLS {} or later"
523
 
                             .format(self._need_version))
524
514
 
525
515
    # Unless otherwise indicated, the constants and types below are
526
516
    # all from the gnutls/gnutls.h C header file.
530
520
    E_INTERRUPTED = -52
531
521
    E_AGAIN = -28
532
522
    CRT_OPENPGP = 2
 
523
    CRT_RAWPK = 3
533
524
    CLIENT = 2
534
525
    SHUT_RDWR = 0
535
526
    CRD_CERTIFICATE = 1
536
527
    E_NO_CERTIFICATE_FOUND = -49
 
528
    X509_FMT_DER = 0
 
529
    NO_TICKETS = 1<<10
 
530
    ENABLE_RAWPK = 1<<18
 
531
    CTYPE_PEERS = 3
 
532
    KEYID_USE_SHA256 = 1        # gnutls/x509.h
537
533
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
538
534
 
539
535
    # Types
562
558
 
563
559
    # Exceptions
564
560
    class Error(Exception):
565
 
        # We need to use the class name "GnuTLS" here, since this
566
 
        # exception might be raised from within GnuTLS.__init__,
567
 
        # which is called before the assignment to the "gnutls"
568
 
        # global variable has happened.
569
561
        def __init__(self, message=None, code=None, args=()):
570
562
            # Default usage is by a message string, but if a return
571
563
            # code is passed, convert it to a string with
572
564
            # gnutls.strerror()
573
565
            self.code = code
574
566
            if message is None and code is not None:
575
 
                message = GnuTLS.strerror(code)
576
 
            return super(GnuTLS.Error, self).__init__(
 
567
                message = gnutls.strerror(code)
 
568
            return super(gnutls.Error, self).__init__(
577
569
                message, *args)
578
570
 
579
571
    class CertificateSecurityError(Error):
593
585
    class ClientSession(object):
594
586
        def __init__(self, socket, credentials=None):
595
587
            self._c_object = gnutls.session_t()
596
 
            gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
 
588
            gnutls_flags = gnutls.CLIENT
 
589
            if gnutls.check_version(b"3.5.6"):
 
590
                gnutls_flags |= gnutls.NO_TICKETS
 
591
            if gnutls.has_rawpk:
 
592
                gnutls_flags |= gnutls.ENABLE_RAWPK
 
593
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
 
594
            del gnutls_flags
597
595
            gnutls.set_default_priority(self._c_object)
598
596
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
599
597
            gnutls.handshake_set_private_extensions(self._c_object,
731
729
    check_version.argtypes = [ctypes.c_char_p]
732
730
    check_version.restype = ctypes.c_char_p
733
731
 
734
 
    # All the function declarations below are from gnutls/openpgp.h
735
 
 
736
 
    openpgp_crt_init = _library.gnutls_openpgp_crt_init
737
 
    openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
738
 
    openpgp_crt_init.restype = _error_code
739
 
 
740
 
    openpgp_crt_import = _library.gnutls_openpgp_crt_import
741
 
    openpgp_crt_import.argtypes = [openpgp_crt_t,
742
 
                                   ctypes.POINTER(datum_t),
743
 
                                   openpgp_crt_fmt_t]
744
 
    openpgp_crt_import.restype = _error_code
745
 
 
746
 
    openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
747
 
    openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
748
 
                                        ctypes.POINTER(ctypes.c_uint)]
749
 
    openpgp_crt_verify_self.restype = _error_code
750
 
 
751
 
    openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
752
 
    openpgp_crt_deinit.argtypes = [openpgp_crt_t]
753
 
    openpgp_crt_deinit.restype = None
754
 
 
755
 
    openpgp_crt_get_fingerprint = (
756
 
        _library.gnutls_openpgp_crt_get_fingerprint)
757
 
    openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
758
 
                                            ctypes.c_void_p,
759
 
                                            ctypes.POINTER(
760
 
                                                ctypes.c_size_t)]
761
 
    openpgp_crt_get_fingerprint.restype = _error_code
 
732
    _need_version = b"3.3.0"
 
733
    if check_version(_need_version) is None:
 
734
        raise self.Error("Needs GnuTLS {} or later"
 
735
                         .format(_need_version))
 
736
 
 
737
    _tls_rawpk_version = b"3.6.6"
 
738
    has_rawpk = bool(check_version(_tls_rawpk_version))
 
739
 
 
740
    if has_rawpk:
 
741
        # Types
 
742
        class pubkey_st(ctypes.Structure):
 
743
            _fields = []
 
744
        pubkey_t = ctypes.POINTER(pubkey_st)
 
745
 
 
746
        x509_crt_fmt_t = ctypes.c_int
 
747
 
 
748
        # All the function declarations below are from gnutls/abstract.h
 
749
        pubkey_init = _library.gnutls_pubkey_init
 
750
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
 
751
        pubkey_init.restype = _error_code
 
752
 
 
753
        pubkey_import = _library.gnutls_pubkey_import
 
754
        pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
 
755
                                  x509_crt_fmt_t]
 
756
        pubkey_import.restype = _error_code
 
757
 
 
758
        pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
 
759
        pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
 
760
                                      ctypes.POINTER(ctypes.c_ubyte),
 
761
                                      ctypes.POINTER(ctypes.c_size_t)]
 
762
        pubkey_get_key_id.restype = _error_code
 
763
 
 
764
        pubkey_deinit = _library.gnutls_pubkey_deinit
 
765
        pubkey_deinit.argtypes = [pubkey_t]
 
766
        pubkey_deinit.restype = None
 
767
    else:
 
768
        # All the function declarations below are from gnutls/openpgp.h
 
769
 
 
770
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
 
771
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
 
772
        openpgp_crt_init.restype = _error_code
 
773
 
 
774
        openpgp_crt_import = _library.gnutls_openpgp_crt_import
 
775
        openpgp_crt_import.argtypes = [openpgp_crt_t,
 
776
                                       ctypes.POINTER(datum_t),
 
777
                                       openpgp_crt_fmt_t]
 
778
        openpgp_crt_import.restype = _error_code
 
779
 
 
780
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
 
781
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
 
782
                                            ctypes.POINTER(ctypes.c_uint)]
 
783
        openpgp_crt_verify_self.restype = _error_code
 
784
 
 
785
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
 
786
        openpgp_crt_deinit.argtypes = [openpgp_crt_t]
 
787
        openpgp_crt_deinit.restype = None
 
788
 
 
789
        openpgp_crt_get_fingerprint = (
 
790
            _library.gnutls_openpgp_crt_get_fingerprint)
 
791
        openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
 
792
                                                ctypes.c_void_p,
 
793
                                                ctypes.POINTER(
 
794
                                                    ctypes.c_size_t)]
 
795
        openpgp_crt_get_fingerprint.restype = _error_code
 
796
 
 
797
    if check_version(b"3.6.4"):
 
798
        certificate_type_get2 = _library.gnutls_certificate_type_get2
 
799
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
 
800
        certificate_type_get2.restype = _error_code
762
801
 
763
802
    # Remove non-public functions
764
803
    del _error_code, _retry_on_error
765
 
# Create the global "gnutls" object, simulating a module
766
 
gnutls = GnuTLS()
767
804
 
768
805
 
769
806
def call_pipe(connection,       # : multiprocessing.Connection
800
837
    disable_initiator_tag: a GLib event source tag, or None
801
838
    enabled:    bool()
802
839
    fingerprint: string (40 or 32 hexadecimal digits); used to
803
 
                 uniquely identify the client
 
840
                 uniquely identify an OpenPGP client
 
841
    key_id: string (64 hexadecimal digits); used to uniquely identify
 
842
            a client using raw public keys
804
843
    host:       string; available for use by the checker command
805
844
    interval:   datetime.timedelta(); How often to start a new checker
806
845
    last_approval_request: datetime.datetime(); (UTC) or None
824
863
    """
825
864
 
826
865
    runtime_expansions = ("approval_delay", "approval_duration",
827
 
                          "created", "enabled", "expires",
 
866
                          "created", "enabled", "expires", "key_id",
828
867
                          "fingerprint", "host", "interval",
829
868
                          "last_approval_request", "last_checked_ok",
830
869
                          "last_enabled", "name", "timeout")
860
899
            client["enabled"] = config.getboolean(client_name,
861
900
                                                  "enabled")
862
901
 
863
 
            # Uppercase and remove spaces from fingerprint for later
864
 
            # comparison purposes with return value from the
865
 
            # fingerprint() function
 
902
            # Uppercase and remove spaces from key_id and fingerprint
 
903
            # for later comparison purposes with return value from the
 
904
            # key_id() and fingerprint() functions
 
905
            client["key_id"] = (section.get("key_id", "").upper()
 
906
                                .replace(" ", ""))
866
907
            client["fingerprint"] = (section["fingerprint"].upper()
867
908
                                     .replace(" ", ""))
868
909
            if "secret" in section:
912
953
            self.expires = None
913
954
 
914
955
        logger.debug("Creating client %r", self.name)
 
956
        logger.debug("  Key ID: %s", self.key_id)
915
957
        logger.debug("  Fingerprint: %s", self.fingerprint)
916
958
        self.created = settings.get("created",
917
959
                                    datetime.datetime.utcnow())
1999
2041
    def Name_dbus_property(self):
2000
2042
        return dbus.String(self.name)
2001
2043
 
 
2044
    # KeyID - property
 
2045
    @dbus_annotations(
 
2046
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
 
2047
    @dbus_service_property(_interface, signature="s", access="read")
 
2048
    def KeyID_dbus_property(self):
 
2049
        return dbus.String(self.key_id)
 
2050
 
2002
2051
    # Fingerprint - property
2003
2052
    @dbus_annotations(
2004
2053
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
2160
2209
 
2161
2210
 
2162
2211
class ProxyClient(object):
2163
 
    def __init__(self, child_pipe, fpr, address):
 
2212
    def __init__(self, child_pipe, key_id, fpr, address):
2164
2213
        self._pipe = child_pipe
2165
 
        self._pipe.send(('init', fpr, address))
 
2214
        self._pipe.send(('init', key_id, fpr, address))
2166
2215
        if not self._pipe.recv():
2167
 
            raise KeyError(fpr)
 
2216
            raise KeyError(key_id or fpr)
2168
2217
 
2169
2218
    def __getattribute__(self, name):
2170
2219
        if name == '_pipe':
2237
2286
 
2238
2287
            approval_required = False
2239
2288
            try:
2240
 
                try:
2241
 
                    fpr = self.fingerprint(
2242
 
                        self.peer_certificate(session))
2243
 
                except (TypeError, gnutls.Error) as error:
2244
 
                    logger.warning("Bad certificate: %s", error)
2245
 
                    return
2246
 
                logger.debug("Fingerprint: %s", fpr)
2247
 
 
2248
 
                try:
2249
 
                    client = ProxyClient(child_pipe, fpr,
 
2289
                if gnutls.has_rawpk:
 
2290
                    fpr = b""
 
2291
                    try:
 
2292
                        key_id = self.key_id(
 
2293
                            self.peer_certificate(session))
 
2294
                    except (TypeError, gnutls.Error) as error:
 
2295
                        logger.warning("Bad certificate: %s", error)
 
2296
                        return
 
2297
                    logger.debug("Key ID: %s", key_id)
 
2298
 
 
2299
                else:
 
2300
                    key_id = b""
 
2301
                    try:
 
2302
                        fpr = self.fingerprint(
 
2303
                            self.peer_certificate(session))
 
2304
                    except (TypeError, gnutls.Error) as error:
 
2305
                        logger.warning("Bad certificate: %s", error)
 
2306
                        return
 
2307
                    logger.debug("Fingerprint: %s", fpr)
 
2308
 
 
2309
                try:
 
2310
                    client = ProxyClient(child_pipe, key_id, fpr,
2250
2311
                                         self.client_address)
2251
2312
                except KeyError:
2252
2313
                    return
2329
2390
 
2330
2391
    @staticmethod
2331
2392
    def peer_certificate(session):
2332
 
        "Return the peer's OpenPGP certificate as a bytestring"
2333
 
        # If not an OpenPGP certificate...
2334
 
        if (gnutls.certificate_type_get(session._c_object)
2335
 
            != gnutls.CRT_OPENPGP):
 
2393
        "Return the peer's certificate as a bytestring"
 
2394
        try:
 
2395
            cert_type = gnutls.certificate_type_get2(session._c_object,
 
2396
                                                     gnutls.CTYPE_PEERS)
 
2397
        except AttributeError:
 
2398
            cert_type = gnutls.certificate_type_get(session._c_object)
 
2399
        if gnutls.has_rawpk:
 
2400
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
 
2401
        else:
 
2402
            valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
 
2403
        # If not a valid certificate type...
 
2404
        if cert_type not in valid_cert_types:
 
2405
            logger.info("Cert type %r not in %r", cert_type,
 
2406
                        valid_cert_types)
2336
2407
            # ...return invalid data
2337
2408
            return b""
2338
2409
        list_size = ctypes.c_uint(1)
2346
2417
        return ctypes.string_at(cert.data, cert.size)
2347
2418
 
2348
2419
    @staticmethod
 
2420
    def key_id(certificate):
 
2421
        "Convert a certificate bytestring to a hexdigit key ID"
 
2422
        # New GnuTLS "datum" with the public key
 
2423
        datum = gnutls.datum_t(
 
2424
            ctypes.cast(ctypes.c_char_p(certificate),
 
2425
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2426
            ctypes.c_uint(len(certificate)))
 
2427
        # XXX all these need to be created in the gnutls "module"
 
2428
        # New empty GnuTLS certificate
 
2429
        pubkey = gnutls.pubkey_t()
 
2430
        gnutls.pubkey_init(ctypes.byref(pubkey))
 
2431
        # Import the raw public key into the certificate
 
2432
        gnutls.pubkey_import(pubkey,
 
2433
                             ctypes.byref(datum),
 
2434
                             gnutls.X509_FMT_DER)
 
2435
        # New buffer for the key ID
 
2436
        buf = ctypes.create_string_buffer(32)
 
2437
        buf_len = ctypes.c_size_t(len(buf))
 
2438
        # Get the key ID from the raw public key into the buffer
 
2439
        gnutls.pubkey_get_key_id(pubkey,
 
2440
                                 gnutls.KEYID_USE_SHA256,
 
2441
                                 ctypes.cast(ctypes.byref(buf),
 
2442
                                             ctypes.POINTER(ctypes.c_ubyte)),
 
2443
                                 ctypes.byref(buf_len))
 
2444
        # Deinit the certificate
 
2445
        gnutls.pubkey_deinit(pubkey)
 
2446
 
 
2447
        # Convert the buffer to a Python bytestring
 
2448
        key_id = ctypes.string_at(buf, buf_len.value)
 
2449
        # Convert the bytestring to hexadecimal notation
 
2450
        hex_key_id = binascii.hexlify(key_id).upper()
 
2451
        return hex_key_id
 
2452
 
 
2453
    @staticmethod
2349
2454
    def fingerprint(openpgp):
2350
2455
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
2351
2456
        # New GnuTLS "datum" with the OpenPGP public key
2500
2605
                    raise
2501
2606
        # Only bind(2) the socket if we really need to.
2502
2607
        if self.server_address[0] or self.server_address[1]:
 
2608
            if self.server_address[1]:
 
2609
                self.allow_reuse_address = True
2503
2610
            if not self.server_address[0]:
2504
2611
                if self.address_family == socket.AF_INET6:
2505
2612
                    any_address = "::"  # in6addr_any
2579
2686
        command = request[0]
2580
2687
 
2581
2688
        if command == 'init':
2582
 
            fpr = request[1].decode("ascii")
2583
 
            address = request[2]
 
2689
            key_id = request[1].decode("ascii")
 
2690
            fpr = request[2].decode("ascii")
 
2691
            address = request[3]
2584
2692
 
2585
2693
            for c in self.clients.values():
2586
 
                if c.fingerprint == fpr:
 
2694
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
 
2695
                    continue
 
2696
                if key_id and c.key_id == key_id:
 
2697
                    client = c
 
2698
                    break
 
2699
                if fpr and c.fingerprint == fpr:
2587
2700
                    client = c
2588
2701
                    break
2589
2702
            else:
2590
 
                logger.info("Client not found for fingerprint: %s, ad"
2591
 
                            "dress: %s", fpr, address)
 
2703
                logger.info("Client not found for key ID: %s, address"
 
2704
                            ": %s", key_id or fpr, address)
2592
2705
                if self.use_dbus:
2593
2706
                    # Emit D-Bus signal
2594
 
                    mandos_dbus_service.ClientNotFound(fpr,
 
2707
                    mandos_dbus_service.ClientNotFound(key_id or fpr,
2595
2708
                                                       address[0])
2596
2709
                parent_pipe.send(False)
2597
2710
                return False
2860
2973
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2861
2974
 
2862
2975
    # Default values for config file for server-global settings
 
2976
    if gnutls.has_rawpk:
 
2977
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
 
2978
                    ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
 
2979
    else:
 
2980
        priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2981
                    ":+SIGN-DSA-SHA256")
2863
2982
    server_defaults = {"interface": "",
2864
2983
                       "address": "",
2865
2984
                       "port": "",
2866
2985
                       "debug": "False",
2867
 
                       "priority":
2868
 
                       "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2869
 
                       ":+SIGN-DSA-SHA256",
 
2986
                       "priority": priority,
2870
2987
                       "servicename": "Mandos",
2871
2988
                       "use_dbus": "True",
2872
2989
                       "use_ipv6": "True",
2877
2994
                       "foreground": "False",
2878
2995
                       "zeroconf": "True",
2879
2996
                       }
 
2997
    del priority
2880
2998
 
2881
2999
    # Parse config file for server-global settings
2882
3000
    server_config = configparser.SafeConfigParser(server_defaults)
3126
3244
                        for k in ("name", "host"):
3127
3245
                            if isinstance(value[k], bytes):
3128
3246
                                value[k] = value[k].decode("utf-8")
 
3247
                        if "key_id" not in value:
 
3248
                            value["key_id"] = ""
 
3249
                        elif "fingerprint" not in value:
 
3250
                            value["fingerprint"] = ""
3129
3251
                    #  old_client_settings
3130
3252
                    # .keys()
3131
3253
                    old_client_settings = {
3268
3390
                pass
3269
3391
 
3270
3392
            @dbus.service.signal(_interface, signature="ss")
3271
 
            def ClientNotFound(self, fingerprint, address):
 
3393
            def ClientNotFound(self, key_id, address):
3272
3394
                "D-Bus signal"
3273
3395
                pass
3274
3396