/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-02-09 23:23:26 UTC
  • Revision ID: teddy@recompile.se-20190209232326-z1z2kzpgfixz7iaj
Add support for using raw public keys in TLS (RFC 7250)

Since GnuTLS removed support for OpenPGP keys in TLS (RFC 6091), and
no other library supports it, we have to change the protocol to use
something else.  We choose to use "raw public keys" (RFC 7250).  Since
we still use OpenPGP keys to decrypt the secret password, this means
that each client will have two keys: One OpenPGP key and one TLS
public/private key, and the key ID of the latter key is used to
identify clients instead of the fingerprint of the OpenPGP key.

Note that this code is still compatible with GnuTLS before version
3.6.0 (when OpenPGP key support was removed).  This commit merely adds
support for using raw pulic keys instead with GnuTLS 3.6.6. or later.

* DBUS-API (Signals/ClientNotFound): Change name of first parameter
                                     from "Fingerprint" to "KeyID".
  (Mandos Client Interface/Properties/KeyID): New.
* INSTALL: Document conflict with GnuTLS 3.6.0 (which removed OpenPGP
           key support) up until 3.6.6, when support for raw public
           keys was added.  Also document new dependency of client on
           "gnutls-bin" package (for certtool).
* Makefile (run-client): Depend on TLS key files, and also pass them
                         as arguments to client.
  (keydir/tls-privkey.pem, keydir/tls-pubkey.pem): New.
  (confdir/clients.conf): Add dependency on TLS public key.
  (purge-client): Add removal of TLS key files.
* clients.conf ([foo]/key_id, [bar]/key_id): New.
* debian/control (Source: mandos/Build-Depends): Also allow
                                                 libgnutls30 (>= 3.6.6)
  (Package: mandos/Depends): - '' -
  (Package: mandos/Description): Alter description to match new
                                 design.
  (Package: mandos-client/Description): - '' -
  (Package: mandos-client/Depends): Move "gnutls-bin | openssl" to
                                    here from "Recommends".
* debian/mandos-client.README.Debian: Add --tls-privkey and
                                      --tls-pubkey options to test
                                      command.
* debian/mandos-client.postinst (create_key): Renamed to "create_keys"
                                             (all callers changed),
                                             and also create TLS key.
* debian/mandos-client.postrm (purge): Also remove TLS key files.
* intro.xml (DESCRIPTION): Describe new dual-key design.
* mandos (GnuTLS): Define different functions depending on whether
                   support for raw public keys is detected.
  (Client.key_id): New attribute.
  (ClientDBus.KeyID_dbus_property): New method.
  (ProxyClient.__init__): Take new "key_id" parameter.
  (ClientHandler.handle): Use key IDs when using raw public keys and
                          use fingerprints when using OpenPGP keys.
  (ClientHandler.peer_certificate): Also handle raw public keys.
  (ClientHandler.key_id): New.
  (MandosServer.handle_ipc): Pass key ID over the pipe IPC.  Also
                             check for key ID matches when looking up
                             clients.
  (main): Default GnuTLS priority string depends on whether we are
          using raw public keys or not.  When unpickling clients, set
          key_id if not set in the pickle.
  (main/MandosDBusService.ClientNotFound): Change name of first
                                           parameter from
                                           "Fingerprint" to "KeyID".
* mandos-clients.conf.xml (OPTIONS): Document new "key_id" option.
  (OPTIONS/secret): Mention new key ID matchning.
  (EXPANSION/RUNTIME EXPANSION): Add new "key_id" option.
  (EXAMPLE): - '' -
* mandos-ctl (tablewords, main/keywords): Add new "KeyID" property.
* mandos-keygen: Create TLS key files.  New "--tls-keytype" (-T)
                 option.  Alter help text to be more clear about key
                 types.  When in password mode, also output "key_id"
                 option.
* mandos-keygen.xml (SYNOPSIS): Add new "--tls-keytype" (-T) option.
  (DESCRIPTION): Alter to match new dual-key design.
  (OVERVIEW): - '' -
  (FILES): Add TLS key files.
* mandos-options.xml (priority): Document new default priority string
                                 when using raw public keys.
* mandos.xml (NETWORK PROTOCOL): Describe new protocol using key ID.
  (BUGS): Remove issue about checking expire times of OpenPGP keys,
          since TLS public keys do not have expiration times.
  (SECURITY/CLIENT): Alter description to match new design.
  (SEE ALSO/GnuTLS): - '' -
  (SEE ALSO): Add reference to RFC 7250, and alter description of when
              RFC 6091 is used.
* overview.xml: Alter text to match new design.
* plugin-runner.xml (EXAMPLE): Add --tls-pubkey and --tls-privkey
                               options to mandos-client options.
* plugins.d/mandos-client.c: Use raw public keys when compiling with
                             supporting GnuTLS versions. Add new
                             "--tls-pubkey" and "--tls-privkey"
                             options (which do nothing if GnuTLS
                             library does not support raw public
                             keys).  Alter text throughout to reflect
                             new design.  Only generate new DH
                             parameters (based on size of OpenPGP key)
                             when using OpenPGP in TLS.  Default
                             GnuTLS priority string depends on whether
                             we are using raw public keys or not.
* plugins.d/mandos-client.xml (SYNOPSIS): Add new "--tls-privkey" (-t)
                                          and "--tls-pubkey" (-T)
                                          options.
  (DESCRIPTION): Describe new dual-key design.
  (OPTIONS): Document new "--tls-privkey" (-t) and "--tls-pubkey" (-T)
             options.
  (OPTIONS/--dh-bits): No longer necessarily depends on OpenPGP key
                       size.
  (FILES): Add default locations for TLS public and private key files.
  (EXAMPLE): Use new --tls-pubkey and --tls-privkey options.
  (SECURITY): Alter wording slightly to reflect new dual-key design.
  (SEE ALSO/GnuTLS): Alter description to match new design.
  (SEE ALSO): Add reference to RFC 7250, and alter description of when
              RFC 6091 is used.

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-2019 Teddy Hogeborn
15
 
# Copyright © 2008-2019 Björn Påhlsson
 
14
# Copyright © 2008-2018 Teddy Hogeborn
 
15
# Copyright © 2008-2018 Björn Påhlsson
16
16
#
17
17
# This file is part of Mandos.
18
18
#
80
80
 
81
81
import dbus
82
82
import dbus.service
83
 
import gi
84
83
from gi.repository import GLib
85
84
from dbus.mainloop.glib import DBusGMainLoop
86
85
import ctypes
116
115
if sys.version_info.major == 2:
117
116
    str = unicode
118
117
 
119
 
if sys.version_info < (3, 2):
120
 
    configparser.Configparser = configparser.SafeConfigParser
121
 
 
122
 
version = "1.8.5"
 
118
version = "1.7.20"
123
119
stored_state_file = "clients.pickle"
124
120
 
125
121
logger = logging.getLogger()
279
275
 
280
276
 
281
277
# Pretend that we have an Avahi module
282
 
class avahi(object):
283
 
    """This isn't so much a class as it is a module-like namespace."""
 
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."""
284
281
    IF_UNSPEC = -1               # avahi-common/address.h
285
282
    PROTO_UNSPEC = -1            # avahi-common/address.h
286
283
    PROTO_INET = 0               # avahi-common/address.h
290
287
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
291
288
    DBUS_PATH_SERVER = "/"
292
289
 
293
 
    @staticmethod
294
 
    def string_array_to_txt_array(t):
 
290
    def string_array_to_txt_array(self, t):
295
291
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
296
292
                           for s in t), signature="ay")
297
293
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
302
298
    SERVER_RUNNING = 2           # avahi-common/defs.h
303
299
    SERVER_COLLISION = 3         # avahi-common/defs.h
304
300
    SERVER_FAILURE = 4           # avahi-common/defs.h
 
301
avahi = Avahi()
305
302
 
306
303
 
307
304
class AvahiError(Exception):
507
504
 
508
505
 
509
506
# Pretend that we have a GnuTLS module
510
 
class gnutls(object):
511
 
    """This isn't so much a class as it is a module-like namespace."""
 
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."""
512
510
 
513
511
    library = ctypes.util.find_library("gnutls")
514
512
    if library is None:
515
513
        library = ctypes.util.find_library("gnutls-deb0")
516
514
    _library = ctypes.cdll.LoadLibrary(library)
517
515
    del library
 
516
    _need_version = b"3.3.0"
 
517
    _tls_rawpk_version = b"3.6.6"
 
518
 
 
519
    def __init__(self):
 
520
        # Need to use "self" here, since this method is called before
 
521
        # the assignment to the "gnutls" global variable happens.
 
522
        if self.check_version(self._need_version) is None:
 
523
            raise self.Error("Needs GnuTLS {} or later"
 
524
                             .format(self._need_version))
518
525
 
519
526
    # Unless otherwise indicated, the constants and types below are
520
527
    # all from the gnutls/gnutls.h C header file.
562
569
 
563
570
    # Exceptions
564
571
    class Error(Exception):
 
572
        # We need to use the class name "GnuTLS" here, since this
 
573
        # exception might be raised from within GnuTLS.__init__,
 
574
        # which is called before the assignment to the "gnutls"
 
575
        # global variable has happened.
565
576
        def __init__(self, message=None, code=None, args=()):
566
577
            # Default usage is by a message string, but if a return
567
578
            # code is passed, convert it to a string with
568
579
            # gnutls.strerror()
569
580
            self.code = code
570
581
            if message is None and code is not None:
571
 
                message = gnutls.strerror(code)
572
 
            return super(gnutls.Error, self).__init__(
 
582
                message = GnuTLS.strerror(code)
 
583
            return super(GnuTLS.Error, self).__init__(
573
584
                message, *args)
574
585
 
575
586
    class CertificateSecurityError(Error):
590
601
        def __init__(self, socket, credentials=None):
591
602
            self._c_object = gnutls.session_t()
592
603
            gnutls_flags = gnutls.CLIENT
593
 
            if gnutls.check_version(b"3.5.6"):
 
604
            if gnutls.check_version("3.5.6"):
594
605
                gnutls_flags |= gnutls.NO_TICKETS
595
606
            if gnutls.has_rawpk:
596
607
                gnutls_flags |= gnutls.ENABLE_RAWPK
733
744
    check_version.argtypes = [ctypes.c_char_p]
734
745
    check_version.restype = ctypes.c_char_p
735
746
 
736
 
    _need_version = b"3.3.0"
737
 
    if check_version(_need_version) is None:
738
 
        raise self.Error("Needs GnuTLS {} or later"
739
 
                         .format(_need_version))
740
 
 
741
 
    _tls_rawpk_version = b"3.6.6"
742
747
    has_rawpk = bool(check_version(_tls_rawpk_version))
743
748
 
744
749
    if has_rawpk:
798
803
                                                    ctypes.c_size_t)]
799
804
        openpgp_crt_get_fingerprint.restype = _error_code
800
805
 
801
 
    if check_version(b"3.6.4"):
 
806
    if check_version("3.6.4"):
802
807
        certificate_type_get2 = _library.gnutls_certificate_type_get2
803
808
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
804
809
        certificate_type_get2.restype = _error_code
805
810
 
806
811
    # Remove non-public functions
807
812
    del _error_code, _retry_on_error
 
813
# Create the global "gnutls" object, simulating a module
 
814
gnutls = GnuTLS()
808
815
 
809
816
 
810
817
def call_pipe(connection,       # : multiprocessing.Connection
825
832
    approved:   bool(); 'None' if not yet approved/disapproved
826
833
    approval_delay: datetime.timedelta(); Time to wait for approval
827
834
    approval_duration: datetime.timedelta(); Duration of one approval
828
 
    checker: multiprocessing.Process(); a running checker process used
829
 
             to see if the client lives. 'None' if no process is
830
 
             running.
 
835
    checker:    subprocess.Popen(); a running checker process used
 
836
                                    to see if the client lives.
 
837
                                    'None' if no process is running.
831
838
    checker_callback_tag: a GLib event source tag, or None
832
839
    checker_command: string; External command which is run to check
833
840
                     if client lives.  %() expansions are done at
1040
1047
    def checker_callback(self, source, condition, connection,
1041
1048
                         command):
1042
1049
        """The checker has completed, so take appropriate actions."""
 
1050
        self.checker_callback_tag = None
 
1051
        self.checker = None
1043
1052
        # Read return code from connection (see call_pipe)
1044
1053
        returncode = connection.recv()
1045
1054
        connection.close()
1046
 
        self.checker.join()
1047
 
        self.checker_callback_tag = None
1048
 
        self.checker = None
1049
1055
 
1050
1056
        if returncode >= 0:
1051
1057
            self.last_checker_status = returncode
2292
2298
            approval_required = False
2293
2299
            try:
2294
2300
                if gnutls.has_rawpk:
2295
 
                    fpr = b""
 
2301
                    fpr = ""
2296
2302
                    try:
2297
2303
                        key_id = self.key_id(
2298
2304
                            self.peer_certificate(session))
2302
2308
                    logger.debug("Key ID: %s", key_id)
2303
2309
 
2304
2310
                else:
2305
 
                    key_id = b""
 
2311
                    key_id = ""
2306
2312
                    try:
2307
2313
                        fpr = self.fingerprint(
2308
2314
                            self.peer_certificate(session))
2610
2616
                    raise
2611
2617
        # Only bind(2) the socket if we really need to.
2612
2618
        if self.server_address[0] or self.server_address[1]:
2613
 
            if self.server_address[1]:
2614
 
                self.allow_reuse_address = True
2615
2619
            if not self.server_address[0]:
2616
2620
                if self.address_family == socket.AF_INET6:
2617
2621
                    any_address = "::"  # in6addr_any
2696
2700
            address = request[3]
2697
2701
 
2698
2702
            for c in self.clients.values():
2699
 
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
2700
 
                    continue
2701
2703
                if key_id and c.key_id == key_id:
2702
2704
                    client = c
2703
2705
                    break
3002
3004
    del priority
3003
3005
 
3004
3006
    # Parse config file for server-global settings
3005
 
    server_config = configparser.ConfigParser(server_defaults)
 
3007
    server_config = configparser.SafeConfigParser(server_defaults)
3006
3008
    del server_defaults
3007
3009
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
3008
 
    # Convert the ConfigParser object to a dict
 
3010
    # Convert the SafeConfigParser object to a dict
3009
3011
    server_settings = server_config.defaults()
3010
3012
    # Use the appropriate methods on the non-string config options
3011
3013
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
3083
3085
                                  server_settings["servicename"])))
3084
3086
 
3085
3087
    # Parse config file with clients
3086
 
    client_config = configparser.ConfigParser(Client.client_defaults)
 
3088
    client_config = configparser.SafeConfigParser(Client
 
3089
                                                  .client_defaults)
3087
3090
    client_config.read(os.path.join(server_settings["configdir"],
3088
3091
                                    "clients.conf"))
3089
3092
 
3160
3163
        # Close all input and output, do double fork, etc.
3161
3164
        daemon()
3162
3165
 
3163
 
    if gi.version_info < (3, 10, 2):
3164
 
        # multiprocessing will use threads, so before we use GLib we
3165
 
        # need to inform GLib that threads will be used.
3166
 
        GLib.threads_init()
 
3166
    # multiprocessing will use threads, so before we use GLib we need
 
3167
    # to inform GLib that threads will be used.
 
3168
    GLib.threads_init()
3167
3169
 
3168
3170
    global main_loop
3169
3171
    # From the Avahi example code
3249
3251
                        for k in ("name", "host"):
3250
3252
                            if isinstance(value[k], bytes):
3251
3253
                                value[k] = value[k].decode("utf-8")
3252
 
                        if "key_id" not in value:
 
3254
                        if not value.has_key("key_id"):
3253
3255
                            value["key_id"] = ""
3254
 
                        elif "fingerprint" not in value:
 
3256
                        elif not value.has_key("fingerprint"):
3255
3257
                            value["fingerprint"] = ""
3256
3258
                    #  old_client_settings
3257
3259
                    # .keys()