=== modified file 'DBUS-API' --- DBUS-API 2018-02-08 10:23:55 +0000 +++ DBUS-API 2019-02-09 23:23:26 +0000 @@ -27,9 +27,9 @@ Removes a client ** Signals: -*** ClientNotFound(s: Fingerprint, s: Address) - A client connected from Address using Fingerprint, but was - rejected because it was not found in the server. The fingerprint +*** ClientNotFound(s: KeyID, s: Address) + A client connected from Address using KeyID, but was + rejected because it was not found in the server. The key ID is represented as a string of hexadecimal digits. The address is an IPv4 or IPv6 address in its normal string format. @@ -66,6 +66,7 @@ | Expires (f) | s | Read | N/A | | ExtendedTimeout (a) | t | Read/Write | extended_timeout | | Fingerprint | s | Read | fingerprint | + | KeyID | s | Read | key_id | | Host | s | Read/Write | host | | Interval (a) | t | Read/Write | interval | | LastApprovalRequest (g) | s | Read | N/A | === modified file 'INSTALL' --- INSTALL 2016-07-03 03:32:28 +0000 +++ INSTALL 2019-02-09 23:23:26 +0000 @@ -39,6 +39,7 @@ *** Mandos Server + GnuTLS 3.3 https://www.gnutls.org/ + (but not 3.6.0 or later, until 3.6.6, which works) + Avahi 0.6.16 http://www.avahi.org/ + Python 2.7 https://www.python.org/ + dbus-python 0.82.4 https://dbus.freedesktop.org/doc/dbus-python/ @@ -60,6 +61,7 @@ + initramfs-tools 0.85i https://tracker.debian.org/pkg/initramfs-tools + GnuTLS 3.3 https://www.gnutls.org/ + (but not 3.6.0 or later, until 3.6.6 which works) + Avahi 0.6.16 http://www.avahi.org/ + GnuPG 1.4.9 https://www.gnupg.org/ + GPGME 1.1.6 https://www.gnupg.org/related_software/gpgme/ @@ -69,7 +71,7 @@ + OpenSSH http://www.openssh.com/ Package names: - initramfs-tools libgnutls-dev libavahi-core-dev gnupg + initramfs-tools libgnutls-dev gnutls-bin libavahi-core-dev gnupg libgpgme11-dev pkg-config ssh * Installing the Mandos server @@ -123,7 +125,9 @@ # /usr/lib/mandos/plugins.d/mandos-client \ --pubkey=/etc/keys/mandos/pubkey.txt \ - --seckey=/etc/keys/mandos/seckey.txt; echo + --seckey=/etc/keys/mandos/seckey.txt \ + --tls-privkey=/etc/keys/mandos/tls-privkey.pem \ + --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem; echo This command should retrieve the password from the server, decrypt it, and output it to standard output. === modified file 'Makefile' --- Makefile 2018-08-19 20:17:48 +0000 +++ Makefile 2019-02-09 23:23:26 +0000 @@ -284,7 +284,7 @@ ./mandos-ctl --check # Run the client with a local config and key -run-client: all keydir/seckey.txt keydir/pubkey.txt +run-client: all keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem @echo "###################################################################" @echo "# The following error messages are harmless and can be safely #" @echo "# ignored: #" @@ -303,12 +303,12 @@ ./plugin-runner --plugin-dir=plugins.d \ --plugin-helper-dir=plugin-helpers \ --config-file=plugin-runner.conf \ - --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--network-hook-dir=network-hooks.d \ + --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--tls-privkey=keydir/tls-privkey.pem,--tls-pubkey=keydir/tls-pubkey.pem,--network-hook-dir=network-hooks.d \ --env-for=mandos-client:GNOME_KEYRING_CONTROL= \ $(CLIENTARGS) # Used by run-client -keydir/seckey.txt keydir/pubkey.txt: mandos-keygen +keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem: mandos-keygen install --directory keydir ./mandos-keygen --dir keydir --force @@ -321,7 +321,7 @@ confdir/mandos.conf: mandos.conf install --directory confdir install --mode=u=rw,go=r $^ $@ -confdir/clients.conf: clients.conf keydir/seckey.txt +confdir/clients.conf: clients.conf keydir/seckey.txt keydir/tls-pubkey.pem install --directory confdir install --mode=u=rw $< $@ # Add a client password @@ -508,7 +508,8 @@ -rmdir $(CONFDIR) purge-client: uninstall-client - -shred --remove $(KEYDIR)/seckey.txt + -shred --remove $(KEYDIR)/seckey.txt $(KEYDIR)/tls-privkey.pem -rm --force $(CONFDIR)/plugin-runner.conf \ - $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt + $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt \ + $(KEYDIR)/tls-pubkey.txt $(KEYDIR)/tls-privkey.txt -rmdir $(KEYDIR) $(CONFDIR)/plugins.d $(CONFDIR) === modified file 'clients.conf' --- clients.conf 2012-06-23 00:58:49 +0000 +++ clients.conf 2019-02-09 23:23:26 +0000 @@ -38,6 +38,9 @@ ;# Example client ;[foo] ; +;# TLS public key ID +;key_id = f33fcbed11ed5e03073f6a55b86ffe92af0e24c045fb6e3b40547b3dc0c030ed +; ;# OpenPGP key fingerprint ;fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 ; @@ -68,6 +71,9 @@ ;#### ;# Another example client, named "bar". ;[bar] +;# The key ID is not space or case sensitive +;key_id = F33FCBED11ED5E03073F6A55B86FFE92 AF0E24C045FB6E3B40547B3DC0C030ED +; ;# The fingerprint is not space or case sensitive ;fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 ; === modified file 'debian/control' --- debian/control 2018-10-14 14:11:15 +0000 +++ debian/control 2019-02-09 23:23:26 +0000 @@ -7,7 +7,8 @@ Build-Depends: debhelper (>= 10), docbook-xml, docbook-xsl, libavahi-core-dev, libgpgme-dev | libgpgme11-dev, libgnutls28-dev (>= 3.3.0) | gnutls-dev (>= 3.3.0), - libgnutls28-dev (<< 3.6.0) | libgnutls30 (<< 3.6.0), + libgnutls28-dev (<< 3.6.0) | libgnutls30 (<< 3.6.0) + | libgnutls30 (>= 3.6.6), xsltproc, pkg-config, libnl-route-3-dev Build-Depends-Indep: systemd, python (>= 2.7), python (<< 3), python-dbus, python-gi @@ -21,7 +22,8 @@ Architecture: all Depends: ${misc:Depends}, python (>= 2.7), python (<< 3), libgnutls28-dev (>= 3.3.0) | libgnutls30 (>= 3.3.0), - libgnutls28-dev (<< 3.6.0) | libgnutls30 (<< 3.6.0), + libgnutls28-dev (<< 3.6.0) | libgnutls30 (<< 3.6.0) + | libgnutls30 (>= 3.6.6), python-dbus, python-gi, avahi-daemon, adduser, python-urwid, gnupg2 | gnupg, systemd-sysv | lsb-base (>= 3.0-6) Recommends: ssh-client | fping @@ -33,10 +35,10 @@ The computers run a small client program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. - The clients are identified by the server using an OpenPGP + The clients are identified by the server using a TLS public key; each client has one unique to it. The server sends the clients an encrypted password. The encrypted password is - decrypted by the clients using the same OpenPGP key, and the + decrypted by the clients using an OpenPGP key, and the password is then used to unlock the root file system, whereupon the computers can continue booting normally. @@ -44,8 +46,9 @@ Architecture: linux-any Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup (<< 2:2.0.3-1) | cryptsetup-initramfs, - initramfs-tools (>= 0.99), dpkg-dev (>=1.16.0) -Recommends: ssh, gnutls-bin | openssl + initramfs-tools (>= 0.99), dpkg-dev (>=1.16.0), + gnutls-bin (>= 3.6.6) | openssl (>= 1.1.0) +Recommends: ssh Breaks: dropbear (<= 0.53.1-1) Enhances: cryptsetup Description: do unattended reboots with an encrypted root file system @@ -56,9 +59,9 @@ The computers run a small client program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. - The clients are identified by the server using an OpenPGP + The clients are identified by the server using a TLS public key; each client has one unique to it. The server sends the clients an encrypted password. The encrypted password is - decrypted by the clients using the same OpenPGP key, and the + decrypted by the clients using an OpenPGP key, and the password is then used to unlock the root file system, whereupon the computers can continue booting normally. === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2016-06-21 19:47:08 +0000 +++ debian/mandos-client.README.Debian 2019-02-09 23:23:26 +0000 @@ -25,7 +25,9 @@ /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH \ )/mandos/plugins.d/mandos-client \ --pubkey=/etc/keys/mandos/pubkey.txt \ - --seckey=/etc/keys/mandos/seckey.txt; echo + --seckey=/etc/keys/mandos/seckey.txt \ + --tls-privkey=/etc/keys/mandos/tls-privkey.pem \ + --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem; echo This command should retrieve the password from the server, decrypt it, and output it to standard output. There it can be verified to @@ -106,4 +108,4 @@ policy or other reasons, simply replace the existing dhparams.pem file and update the initital RAM disk image. - -- Teddy Hogeborn , Tue, 21 Jun 2016 21:43:49 +0200 + -- Teddy Hogeborn , Sat, 9 Feb 2019 15:08:04 +0100 === modified file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2016-10-09 22:35:41 +0000 +++ debian/mandos-client.postinst 2019-02-09 23:23:26 +0000 @@ -50,14 +50,57 @@ fi } -# Create client key pair -create_key(){ - if [ -r /etc/keys/mandos/pubkey.txt \ - -a -r /etc/keys/mandos/seckey.txt ]; then - return 0 - fi - mandos-keygen - gpg-connect-agent KILLAGENT /bye || : +# Create client key pairs +create_keys(){ + # If the OpenPGP key files do not exist, generate all keys using + # mandos-keygen + if ! [ -r /etc/keys/mandos/pubkey.txt \ + -a -r /etc/keys/mandos/seckey.txt ]; then + mandos-keygen + gpg-connect-agent KILLAGENT /bye || : + return 0 + fi + + # If the TLS keys already exists, do nothing + if [ -r /etc/keys/mandos/tls-privkey.pem \ + -a -r /etc/keys/mandos/tls-pubkey.pem ]; then + return 0 + fi + + # If this is an upgrade from an old installation, the TLS keys + # will not exist; create them. + + # First try certtool from GnuTLS + if ! certtool --generate-privkey --password='' \ + --outfile /etc/keys/mandos/tls-privkey.pem \ + --sec-param ultra --key-type=ed25519 --pkcs8 --no-text \ + 2>/dev/null; then + # Otherwise try OpenSSL + if ! openssl genpkey -algorithm X25519 \ + -out /etc/keys/mandos/tls-privkey.pem; then + rm --force /etc/keys/mandos/tls-privkey.pem + # None of the commands succeded; give up + return 1 + fi + fi + + local umask=$(umask) + umask 077 + # First try certtool from GnuTLS + if ! certtool --password='' \ + --load-privkey=/etc/keys/mandos/tls-privkey.pem \ + --outfile=/etc/keys/mandos/tls-pubkey.pem --pubkey-info \ + --no-text 2>/dev/null; then + # Otherwise try OpenSSL + if ! openssl pkey -in /etc/keys/mandos/tls-privkey.pem \ + -out /etc/keys/mandos/tls-pubkey.pem -pubout; then + rm --force /etc/keys/mandos/tls-pubkey.pem + # None of the commands succeded; give up + umask $umask + return 1 + fi + fi + umask $umask } create_dh_params(){ @@ -88,7 +131,7 @@ case "$1" in configure) add_mandos_user "$@" - create_key "$@" + create_keys "$@" create_dh_params "$@" || : update_initramfs "$@" if dpkg --compare-versions "$2" lt-nl "1.7.10-1"; then === modified file 'debian/mandos-client.postrm' --- debian/mandos-client.postrm 2015-07-27 09:23:56 +0000 +++ debian/mandos-client.postrm 2019-02-09 23:23:26 +0000 @@ -44,6 +44,8 @@ rm --force /etc/mandos/plugin-runner.conf \ /etc/keys/mandos/pubkey.txt \ /etc/keys/mandos/seckey.txt \ + /etc/keys/mandos/tls-privkey.pem \ + /etc/keys/mandos/tls-pubkey.pem \ /etc/keys/mandos/dhparams.pem 2>/dev/null update_initramfs ;; === modified file 'intro.xml' --- intro.xml 2018-02-08 10:23:55 +0000 +++ intro.xml 2019-02-09 23:23:26 +0000 @@ -1,7 +1,7 @@ + %common; ]> @@ -67,10 +67,10 @@ The computers run a small client program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. The clients - are identified by the server using an OpenPGP key; each client + are identified by the server using a TLS public key; each client has one unique to it. The server sends the clients an encrypted password. The encrypted password is decrypted by the clients - using the same OpenPGP key, and the password is then used to + using a separate OpenPGP key, and the password is then used to unlock the root file system, whereupon the computers can continue booting normally. @@ -200,8 +200,8 @@ No. The server only gives out the passwords to clients which have in the TLS handshake proven that - they do indeed hold the OpenPGP private key corresponding to - that client. + they do indeed hold the private key corresponding to that + client. === modified file 'mandos' --- mandos 2018-08-19 20:17:48 +0000 +++ mandos 2019-02-09 23:23:26 +0000 @@ -514,6 +514,7 @@ _library = ctypes.cdll.LoadLibrary(library) del library _need_version = b"3.3.0" + _tls_rawpk_version = b"3.6.6" def __init__(self): # Need to use "self" here, since this method is called before @@ -530,10 +531,16 @@ E_INTERRUPTED = -52 E_AGAIN = -28 CRT_OPENPGP = 2 + CRT_RAWPK = 3 CLIENT = 2 SHUT_RDWR = 0 CRD_CERTIFICATE = 1 E_NO_CERTIFICATE_FOUND = -49 + X509_FMT_DER = 0 + NO_TICKETS = 1<<10 + ENABLE_RAWPK = 1<<18 + CTYPE_PEERS = 3 + KEYID_USE_SHA256 = 1 # gnutls/x509.h OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h # Types @@ -593,7 +600,13 @@ class ClientSession(object): def __init__(self, socket, credentials=None): self._c_object = gnutls.session_t() - gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT) + gnutls_flags = gnutls.CLIENT + if gnutls.check_version("3.5.6"): + gnutls_flags |= gnutls.NO_TICKETS + if gnutls.has_rawpk: + gnutls_flags |= gnutls.ENABLE_RAWPK + gnutls.init(ctypes.byref(self._c_object), gnutls_flags) + del gnutls_flags gnutls.set_default_priority(self._c_object) gnutls.transport_set_ptr(self._c_object, socket.fileno()) gnutls.handshake_set_private_extensions(self._c_object, @@ -731,34 +744,69 @@ check_version.argtypes = [ctypes.c_char_p] check_version.restype = ctypes.c_char_p - # All the function declarations below are from gnutls/openpgp.h - - openpgp_crt_init = _library.gnutls_openpgp_crt_init - openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)] - openpgp_crt_init.restype = _error_code - - openpgp_crt_import = _library.gnutls_openpgp_crt_import - openpgp_crt_import.argtypes = [openpgp_crt_t, - ctypes.POINTER(datum_t), - openpgp_crt_fmt_t] - openpgp_crt_import.restype = _error_code - - openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self - openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint, - ctypes.POINTER(ctypes.c_uint)] - openpgp_crt_verify_self.restype = _error_code - - openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit - openpgp_crt_deinit.argtypes = [openpgp_crt_t] - openpgp_crt_deinit.restype = None - - openpgp_crt_get_fingerprint = ( - _library.gnutls_openpgp_crt_get_fingerprint) - openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t, - ctypes.c_void_p, - ctypes.POINTER( - ctypes.c_size_t)] - openpgp_crt_get_fingerprint.restype = _error_code + has_rawpk = bool(check_version(_tls_rawpk_version)) + + if has_rawpk: + # Types + class pubkey_st(ctypes.Structure): + _fields = [] + pubkey_t = ctypes.POINTER(pubkey_st) + + x509_crt_fmt_t = ctypes.c_int + + # All the function declarations below are from gnutls/abstract.h + pubkey_init = _library.gnutls_pubkey_init + pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)] + pubkey_init.restype = _error_code + + pubkey_import = _library.gnutls_pubkey_import + pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t), + x509_crt_fmt_t] + pubkey_import.restype = _error_code + + pubkey_get_key_id = _library.gnutls_pubkey_get_key_id + pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int, + ctypes.POINTER(ctypes.c_ubyte), + ctypes.POINTER(ctypes.c_size_t)] + pubkey_get_key_id.restype = _error_code + + pubkey_deinit = _library.gnutls_pubkey_deinit + pubkey_deinit.argtypes = [pubkey_t] + pubkey_deinit.restype = None + else: + # All the function declarations below are from gnutls/openpgp.h + + openpgp_crt_init = _library.gnutls_openpgp_crt_init + openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)] + openpgp_crt_init.restype = _error_code + + openpgp_crt_import = _library.gnutls_openpgp_crt_import + openpgp_crt_import.argtypes = [openpgp_crt_t, + ctypes.POINTER(datum_t), + openpgp_crt_fmt_t] + openpgp_crt_import.restype = _error_code + + openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self + openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint, + ctypes.POINTER(ctypes.c_uint)] + openpgp_crt_verify_self.restype = _error_code + + openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit + openpgp_crt_deinit.argtypes = [openpgp_crt_t] + openpgp_crt_deinit.restype = None + + openpgp_crt_get_fingerprint = ( + _library.gnutls_openpgp_crt_get_fingerprint) + openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t, + ctypes.c_void_p, + ctypes.POINTER( + ctypes.c_size_t)] + openpgp_crt_get_fingerprint.restype = _error_code + + if check_version("3.6.4"): + certificate_type_get2 = _library.gnutls_certificate_type_get2 + certificate_type_get2.argtypes = [session_t, ctypes.c_int] + certificate_type_get2.restype = _error_code # Remove non-public functions del _error_code, _retry_on_error @@ -800,7 +848,9 @@ disable_initiator_tag: a GLib event source tag, or None enabled: bool() fingerprint: string (40 or 32 hexadecimal digits); used to - uniquely identify the client + uniquely identify an OpenPGP client + key_id: string (64 hexadecimal digits); used to uniquely identify + a client using raw public keys host: string; available for use by the checker command interval: datetime.timedelta(); How often to start a new checker last_approval_request: datetime.datetime(); (UTC) or None @@ -824,7 +874,7 @@ """ runtime_expansions = ("approval_delay", "approval_duration", - "created", "enabled", "expires", + "created", "enabled", "expires", "key_id", "fingerprint", "host", "interval", "last_approval_request", "last_checked_ok", "last_enabled", "name", "timeout") @@ -860,9 +910,11 @@ client["enabled"] = config.getboolean(client_name, "enabled") - # Uppercase and remove spaces from fingerprint for later - # comparison purposes with return value from the - # fingerprint() function + # Uppercase and remove spaces from key_id and fingerprint + # for later comparison purposes with return value from the + # key_id() and fingerprint() functions + client["key_id"] = (section.get("key_id", "").upper() + .replace(" ", "")) client["fingerprint"] = (section["fingerprint"].upper() .replace(" ", "")) if "secret" in section: @@ -912,6 +964,7 @@ self.expires = None logger.debug("Creating client %r", self.name) + logger.debug(" Key ID: %s", self.key_id) logger.debug(" Fingerprint: %s", self.fingerprint) self.created = settings.get("created", datetime.datetime.utcnow()) @@ -1999,6 +2052,13 @@ def Name_dbus_property(self): return dbus.String(self.name) + # KeyID - property + @dbus_annotations( + {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) + @dbus_service_property(_interface, signature="s", access="read") + def KeyID_dbus_property(self): + return dbus.String(self.key_id) + # Fingerprint - property @dbus_annotations( {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) @@ -2160,11 +2220,11 @@ class ProxyClient(object): - def __init__(self, child_pipe, fpr, address): + def __init__(self, child_pipe, key_id, fpr, address): self._pipe = child_pipe - self._pipe.send(('init', fpr, address)) + self._pipe.send(('init', key_id, fpr, address)) if not self._pipe.recv(): - raise KeyError(fpr) + raise KeyError(key_id or fpr) def __getattribute__(self, name): if name == '_pipe': @@ -2237,16 +2297,28 @@ approval_required = False try: - try: - fpr = self.fingerprint( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - logger.warning("Bad certificate: %s", error) - return - logger.debug("Fingerprint: %s", fpr) - - try: - client = ProxyClient(child_pipe, fpr, + if gnutls.has_rawpk: + fpr = "" + try: + key_id = self.key_id( + self.peer_certificate(session)) + except (TypeError, gnutls.Error) as error: + logger.warning("Bad certificate: %s", error) + return + logger.debug("Key ID: %s", key_id) + + else: + key_id = "" + try: + fpr = self.fingerprint( + self.peer_certificate(session)) + except (TypeError, gnutls.Error) as error: + logger.warning("Bad certificate: %s", error) + return + logger.debug("Fingerprint: %s", fpr) + + try: + client = ProxyClient(child_pipe, key_id, fpr, self.client_address) except KeyError: return @@ -2329,10 +2401,20 @@ @staticmethod def peer_certificate(session): - "Return the peer's OpenPGP certificate as a bytestring" - # If not an OpenPGP certificate... - if (gnutls.certificate_type_get(session._c_object) - != gnutls.CRT_OPENPGP): + "Return the peer's certificate as a bytestring" + try: + cert_type = gnutls.certificate_type_get2(session._c_object, + gnutls.CTYPE_PEERS) + except AttributeError: + cert_type = gnutls.certificate_type_get(session._c_object) + if gnutls.has_rawpk: + valid_cert_types = frozenset((gnutls.CRT_RAWPK,)) + else: + valid_cert_types = frozenset((gnutls.CRT_OPENPGP,)) + # If not a valid certificate type... + if cert_type not in valid_cert_types: + logger.info("Cert type %r not in %r", cert_type, + valid_cert_types) # ...return invalid data return b"" list_size = ctypes.c_uint(1) @@ -2346,6 +2428,40 @@ return ctypes.string_at(cert.data, cert.size) @staticmethod + def key_id(certificate): + "Convert a certificate bytestring to a hexdigit key ID" + # New GnuTLS "datum" with the public key + datum = gnutls.datum_t( + ctypes.cast(ctypes.c_char_p(certificate), + ctypes.POINTER(ctypes.c_ubyte)), + ctypes.c_uint(len(certificate))) + # XXX all these need to be created in the gnutls "module" + # New empty GnuTLS certificate + pubkey = gnutls.pubkey_t() + gnutls.pubkey_init(ctypes.byref(pubkey)) + # Import the raw public key into the certificate + gnutls.pubkey_import(pubkey, + ctypes.byref(datum), + gnutls.X509_FMT_DER) + # New buffer for the key ID + buf = ctypes.create_string_buffer(32) + buf_len = ctypes.c_size_t(len(buf)) + # Get the key ID from the raw public key into the buffer + gnutls.pubkey_get_key_id(pubkey, + gnutls.KEYID_USE_SHA256, + ctypes.cast(ctypes.byref(buf), + ctypes.POINTER(ctypes.c_ubyte)), + ctypes.byref(buf_len)) + # Deinit the certificate + gnutls.pubkey_deinit(pubkey) + + # Convert the buffer to a Python bytestring + key_id = ctypes.string_at(buf, buf_len.value) + # Convert the bytestring to hexadecimal notation + hex_key_id = binascii.hexlify(key_id).upper() + return hex_key_id + + @staticmethod def fingerprint(openpgp): "Convert an OpenPGP bytestring to a hexdigit fingerprint" # New GnuTLS "datum" with the OpenPGP public key @@ -2579,19 +2695,23 @@ command = request[0] if command == 'init': - fpr = request[1].decode("ascii") - address = request[2] + key_id = request[1].decode("ascii") + fpr = request[2].decode("ascii") + address = request[3] for c in self.clients.values(): - if c.fingerprint == fpr: + if key_id and c.key_id == key_id: + client = c + break + if fpr and c.fingerprint == fpr: client = c break else: - logger.info("Client not found for fingerprint: %s, ad" - "dress: %s", fpr, address) + logger.info("Client not found for key ID: %s, address" + ": %s", key_id or fpr, address) if self.use_dbus: # Emit D-Bus signal - mandos_dbus_service.ClientNotFound(fpr, + mandos_dbus_service.ClientNotFound(key_id or fpr, address[0]) parent_pipe.send(False) return False @@ -2860,13 +2980,17 @@ sys.exit(os.EX_OK if fail_count == 0 else 1) # Default values for config file for server-global settings + if gnutls.has_rawpk: + priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA" + ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA") + else: + priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA" + ":+SIGN-DSA-SHA256") server_defaults = {"interface": "", "address": "", "port": "", "debug": "False", - "priority": - "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA" - ":+SIGN-DSA-SHA256", + "priority": priority, "servicename": "Mandos", "use_dbus": "True", "use_ipv6": "True", @@ -2877,6 +3001,7 @@ "foreground": "False", "zeroconf": "True", } + del priority # Parse config file for server-global settings server_config = configparser.SafeConfigParser(server_defaults) @@ -3126,6 +3251,10 @@ for k in ("name", "host"): if isinstance(value[k], bytes): value[k] = value[k].decode("utf-8") + if not value.has_key("key_id"): + value["key_id"] = "" + elif not value.has_key("fingerprint"): + value["fingerprint"] = "" # old_client_settings # .keys() old_client_settings = { @@ -3268,7 +3397,7 @@ pass @dbus.service.signal(_interface, signature="ss") - def ClientNotFound(self, fingerprint, address): + def ClientNotFound(self, key_id, address): "D-Bus signal" pass === modified file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 2018-02-08 10:23:55 +0000 +++ mandos-clients.conf.xml 2019-02-09 23:23:26 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -239,6 +239,22 @@ + + + + This option is optional. + + + This option sets the certificate key ID that identifies + the public key that clients authenticate themselves with + through TLS. The string needs to be in hexadecimal form, + but spaces or upper/lower case are not significant. + + + + + @@ -314,9 +330,9 @@ If present, this option must be set to a string of base64-encoded binary data. It will be decoded and sent - to the client matching the above - . This should, of course, be - OpenPGP encrypted data, decryptable only by the client. + to the client matching the above + or . This should, of course, + be OpenPGP encrypted data, decryptable only by the client. The program mandos-keygen8 can, using its @@ -417,6 +433,7 @@ created, enabled, expires, + key_id, fingerprint, host, interval, @@ -479,6 +496,7 @@ # Client "foo" [foo] +key_id = 788cd77115cd0bb7b2d5e0ae8496f6b48149d5e712c652076b1fd2d957ef7c1f fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 secret = hQIOA6QdEjBs2L/HEAf/TCyrDe5Xnm9esa+Pb/vWF9CUqfn4srzVgSu234 @@ -501,6 +519,7 @@ # Client "bar" [bar] +key_id = F90C7A81D72D1EA69A51031A91FF8885F36C8B46D155C8C58709A4C99AE9E361 fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 secfile = /etc/mandos/bar-secret timeout = PT15M === modified file 'mandos-ctl' --- mandos-ctl 2018-08-19 20:17:48 +0000 +++ mandos-ctl 2019-02-09 23:23:26 +0000 @@ -58,6 +58,7 @@ "Interval": "Interval", "Host": "Host", "Fingerprint": "Fingerprint", + "KeyID": "Key ID", "CheckerRunning": "Check Is Running", "LastEnabled": "Last Enabled", "ApprovalPending": "Approval Is Pending", @@ -404,12 +405,12 @@ if not has_actions(options) and clients: if options.verbose or options.dump_json: keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK", - "Created", "Interval", "Host", "Fingerprint", - "CheckerRunning", "LastEnabled", - "ApprovalPending", "ApprovedByDefault", - "LastApprovalRequest", "ApprovalDelay", - "ApprovalDuration", "Checker", - "ExtendedTimeout", "Expires", + "Created", "Interval", "Host", "KeyID", + "Fingerprint", "CheckerRunning", + "LastEnabled", "ApprovalPending", + "ApprovedByDefault", "LastApprovalRequest", + "ApprovalDelay", "ApprovalDuration", + "Checker", "ExtendedTimeout", "Expires", "LastCheckerStatus") else: keywords = defaultkeywords === modified file 'mandos-keygen' --- mandos-keygen 2018-08-19 20:17:48 +0000 +++ mandos-keygen 2019-02-09 23:23:26 +0000 @@ -1,6 +1,6 @@ #!/bin/sh -e # -# Mandos key generator - create a new OpenPGP key for a Mandos client +# Mandos key generator - create new keys for a Mandos client # # Copyright © 2008-2018 Teddy Hogeborn # Copyright © 2008-2018 Björn Påhlsson @@ -34,6 +34,7 @@ KEYEMAIL="" KEYCOMMENT="" KEYEXPIRE=0 +TLS_KEYTYPE=ed25519 FORCE=no SSH=yes KEYCOMMENT_ORIG="$KEYCOMMENT" @@ -44,8 +45,8 @@ fi # Parse options -TEMP=`getopt --options vhpF:d:t:l:s:L:n:e:c:x:fS \ - --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,force,no-ssh \ +TEMP=`getopt --options vhpF:d:t:l:s:L:n:e:c:x:T:fS \ + --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,tls-keytype:,force,no-ssh \ --name "$0" -- "$@"` help(){ @@ -63,21 +64,23 @@ -v, --version Show program's version number and exit -h, --help Show this help message and exit -d DIR, --dir DIR Target directory for key files - -t TYPE, --type TYPE Key type. Default is RSA. + -t TYPE, --type TYPE OpenPGP key type. Default is RSA. -l BITS, --length BITS - Key length in bits. Default is 4096. + OpenPGP key length in bits. Default is 4096. -s TYPE, --subtype TYPE - Subkey type. Default is RSA. + OpenPGP subkey type. Default is RSA. -L BITS, --sublength BITS - Subkey length in bits. Default is 4096. + OpenPGP subkey length in bits. Default 4096. -n NAME, --name NAME Name of key. Default is the FQDN. -e ADDRESS, --email ADDRESS - Email address of key. Default is empty. + Email address of OpenPGP key. Default empty. -c TEXT, --comment TEXT - Comment field for key. The default is empty. + Comment field for OpenPGP key. Default empty. -x TIME, --expire TIME - Key expire time. Default is no expiration. + OpenPGP key expire time. Default is none. See gpg(1) for syntax. + -T TYPE, --tls-keytype TYPE + TLS key type. Default is ed25519. -f, --force Force overwriting old key files. Password creation options: @@ -106,6 +109,7 @@ -e|--email) KEYEMAIL="$2"; shift 2;; -c|--comment) KEYCOMMENT="$2"; shift 2;; -x|--expire) KEYEXPIRE="$2"; shift 2;; + -T|--tls-keytype) TLS_KEYTYPE="$2"; shift 2;; -f|--force) FORCE=yes; shift;; -S|--no-ssh) SSH=no; shift;; -v|--version) echo "$0 $VERSION"; exit;; @@ -121,6 +125,8 @@ SECKEYFILE="$KEYDIR/seckey.txt" PUBKEYFILE="$KEYDIR/pubkey.txt" +TLS_PRIVKEYFILE="$KEYDIR/tls-privkey.pem" +TLS_PUBKEYFILE="$KEYDIR/tls-pubkey.pem" # Check for some invalid values if [ ! -d "$KEYDIR" ]; then @@ -163,7 +169,9 @@ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;; esac - if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ]; } \ + if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ] \ + || [ -e "$TLS_PRIVKEYFILE" ] \ + || [ -e "$TLS_PUBKEYFILE" ]; } \ && [ "$FORCE" -eq 0 ]; then echo "Refusing to overwrite old key files; use --force" >&2 exit 1 @@ -234,6 +242,46 @@ date fi + # Backup any old key files + if cp --backup=numbered --force "$TLS_PRIVKEYFILE" "$TLS_PRIVKEYFILE" \ + 2>/dev/null; then + shred --remove "$TLS_PRIVKEYFILE" + fi + if cp --backup=numbered --force "$TLS_PUBKEYFILE" "$TLS_PUBKEYFILE" \ + 2>/dev/null; then + rm --force "$TLS_PUBKEYFILE" + fi + + ## Generate TLS private key + + # First try certtool from GnuTLS + if ! certtool --generate-privkey --password='' \ + --outfile "$TLS_PRIVKEYFILE" --sec-param ultra \ + --key-type="$TLS_KEYTYPE" --pkcs8 --no-text 2>/dev/null; then + # Otherwise try OpenSSL + if ! openssl genpkey -algorithm X25519 -out \ + /etc/keys/mandos/tls-privkey.pem; then + rm --force /etc/keys/mandos/tls-privkey.pem + # None of the commands succeded; give up + return 1 + fi + fi + + ## TLS public key + + # First try certtool from GnuTLS + if ! certtool --password='' --load-privkey="$TLS_PRIVKEYFILE" \ + --outfile="$TLS_PUBKEYFILE" --pubkey-info --no-text \ + 2>/dev/null; then + # Otherwise try OpenSSL + if ! openssl pkey -in "$TLS_PRIVKEYFILE" \ + -out "$TLS_PUBKEYFILE" -pubout; then + rm --force "$TLS_PUBKEYFILE" + # None of the commands succeded; give up + return 1 + fi + fi + # Make sure trustdb.gpg exists; # this is a workaround for Debian bug #737128 gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ @@ -321,6 +369,17 @@ test -n "$FINGERPRINT" + KEY_ID="$(certtool --key-id --hash=sha256 \ + --infile="$TLS_PUBKEYFILE" 2>/dev/null || :)" + + if [ -z "$KEY_ID" ]; then + KEY_ID=$(openssl pkey -pubin -in /tmp/tls-pubkey.pem \ + -outform der \ + | openssl sha256 \ + | sed --expression='s/^.*[^[:xdigit:]]//') + fi + test -n "$KEY_ID" + FILECOMMENT="Encrypted password for a Mandos client" while [ ! -s "$SECFILE" ]; do @@ -360,6 +419,7 @@ cat <<-EOF [$KEYNAME] host = $KEYNAME + key_id = $KEY_ID fingerprint = $FINGERPRINT secret = EOF === modified file 'mandos-keygen.xml' --- mandos-keygen.xml 2018-02-08 10:23:55 +0000 +++ mandos-keygen.xml 2019-02-09 23:23:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -127,6 +127,13 @@ + + + + + @@ -180,9 +187,9 @@ DESCRIPTION &COMMANDNAME; is a program to generate the - OpenPGP key used by + TLS and OpenPGP keys used by mandos-client - 8mandos. The key is + 8mandos. The keys are normally written to /etc/mandos for later installation into the initrd image, but this, and most other things, can be changed with command line options. @@ -241,7 +248,7 @@ TYPE - Key type. Default is RSA. + OpenPGP key type. Default is RSA. @@ -253,7 +260,7 @@ BITS - Key length in bits. Default is 4096. + OpenPGP key length in bits. Default is 4096. @@ -265,8 +272,7 @@ KEYTYPE - Subkey type. Default is RSA (Elgamal - encryption-only). + OpenPGP subkey type. Default is RSA @@ -278,7 +284,7 @@ BITS - Subkey length in bits. Default is 4096. + OpenPGP subkey length in bits. Default is 4096. @@ -322,6 +328,18 @@ + + + + + TLS key type. Default is ed25519 + + + + + @@ -383,9 +401,9 @@ OVERVIEW - This program is a small utility to generate new OpenPGP keys for - new Mandos clients, and to generate sections for inclusion in - clients.conf on the server. + This program is a small utility to generate new TLS and OpenPGP + keys for new Mandos clients, and to generate sections for + inclusion in clients.conf on the server. @@ -441,6 +459,22 @@ + /etc/keys/mandos/tls-privkey.pem + + + Private key file which will be created or overwritten. + + + + + /etc/keys/mandos/tls-pubkey.pem + + + Public key file which will be created or overwritten. + + + + /tmp === modified file 'mandos-options.xml' --- mandos-options.xml 2015-07-20 03:03:33 +0000 +++ mandos-options.xml 2019-02-09 23:23:26 +0000 @@ -48,10 +48,11 @@ GnuTLS priority string for the TLS handshake. - The default is SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA - :+SIGN-DSA-SHA256. - See SECURE128​:!CTYPE-X.509​:+CTYPE-RAWPK​:!RSA​:!VERS-ALL​:+VERS-TLS1.3​:%PROFILE_ULTRA + when using raw public keys in TLS, and + SECURE256​:!CTYPE-X.509​:+CTYPE-OPENPGP​:!RSA​:+SIGN-DSA-SHA256 + when using OpenPGP keys in TLS,. See gnutls_priority_init 3 for the syntax. Warning: changing this may make the === modified file 'mandos.xml' --- mandos.xml 2018-02-08 10:23:55 +0000 +++ mandos.xml 2019-02-09 23:23:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -361,11 +361,11 @@ start a TLS protocol handshake with a slight quirk: the Mandos server program acts as a TLS client while the connecting Mandos client acts as a TLS server. - The Mandos client must supply an OpenPGP certificate, and the - fingerprint of this certificate is used by the Mandos server to - look up (in a list read from clients.conf - at start time) which binary blob to give the client. No other - authentication or authorization is done by the server. + The Mandos client must supply a TLS public key, and the key ID + of this public key is used by the Mandos server to look up (in a + list read from clients.conf at start time) + which binary blob to give the client. No other authentication + or authorization is done by the server. Mandos Protocol (Version 1) @@ -391,7 +391,7 @@ - OpenPGP public key (part of TLS handshake) + Public key (part of TLS handshake) -> @@ -586,10 +586,6 @@ There is no fine-grained control over logging and debug output. - - This server does not check the expire time of clients’ OpenPGP - keys. - @@ -646,12 +642,12 @@ CLIENTS The server only gives out its stored data to clients which - does have the OpenPGP key of the stored fingerprint. This is - guaranteed by the fact that the client sends its OpenPGP - public key in the TLS handshake; this ensures it to be - genuine. The server computes the fingerprint of the key - itself and looks up the fingerprint in its list of - clients. The clients.conf file (see + does have the correct key ID of the stored key ID. This is + guaranteed by the fact that the client sends its public key in + the TLS handshake; this ensures it to be genuine. The server + computes the key ID of the key itself and looks up the key ID + in its list of clients. The clients.conf + file (see mandos-clients.conf 5) must be made non-readable by anyone @@ -715,7 +711,7 @@ GnuTLS is the library this server uses to implement TLS for communicating securely with the client, and at the same time - confidently get the client’s public OpenPGP key. + confidently get the client’s public key. @@ -774,13 +770,28 @@ + RFC 7250: Using Raw Public Keys in Transport + Layer Security (TLS) and Datagram Transport Layer Security + (DTLS) + + + + This is implemented by GnuTLS version 3.6.6 and is, if + present, used by this server so that raw public keys can be + used. + + + + + RFC 6091: Using OpenPGP Keys for Transport Layer Security (TLS) Authentication - This is implemented by GnuTLS and used by this server so - that OpenPGP keys can be used. + This is implemented by GnuTLS before version 3.6.0 and is, + if present, used by this server so that OpenPGP keys can be + used. === modified file 'overview.xml' --- overview.xml 2008-09-13 15:36:18 +0000 +++ overview.xml 2019-02-09 23:23:26 +0000 @@ -8,10 +8,10 @@ program in the initial RAM disk environment which will communicate with a server over a network. All network communication is encrypted using TLS. The - clients are identified by the server using an OpenPGP key; each - client has one unique to it. The server sends the clients an - encrypted password. The encrypted password is decrypted by the - clients using the same OpenPGP key, and the password is then used to - unlock the root file system, whereupon the computers can continue - booting normally. + clients are identified by the server using a TLS key; each client + has one unique to it. The server sends the clients an encrypted + password. The encrypted password is decrypted by the clients using + a separate OpenPGP key, and the password is then used to unlock the + root file system, whereupon the computers can continue booting + normally. === modified file 'plugin-runner.xml' --- plugin-runner.xml 2018-02-08 10:23:55 +0000 +++ plugin-runner.xml 2019-02-09 23:23:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -634,7 +634,7 @@ -cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/x86_64-linux-gnu/mandos/plugins.d --plugin-helper-dir /usr/lib/x86_64-linux-gnu/mandos/plugin-helpers --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt +cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/x86_64-linux-gnu/mandos/plugins.d --plugin-helper-dir /usr/lib/x86_64-linux-gnu/mandos/plugin-helpers --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt,--tls-pubkey=tls-pubkey.pem,--tls-privkey=tls-privkey.pem === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2018-08-15 09:26:02 +0000 +++ plugins.d/mandos-client.c 2019-02-09 23:23:26 +0000 @@ -123,9 +123,15 @@ gnutls_* init_gnutls_session(), GNUTLS_* */ +#if GNUTLS_VERSION_NUMBER < 0x030600 #include /* gnutls_certificate_set_openpgp_key_file(), GNUTLS_OPENPGP_FMT_BASE64 */ +#elif GNUTLS_VERSION_NUMBER >= 0x030606 +#include /* gnutls_pkcs_encrypt_flags_t, + GNUTLS_PKCS_PLAIN, + GNUTLS_PKCS_NULL_PASSWORD */ +#endif /* GPGME */ #include /* All GPGME types, constants and @@ -139,6 +145,8 @@ #define PATHDIR "/conf/conf.d/mandos" #define SECKEY "seckey.txt" #define PUBKEY "pubkey.txt" +#define TLS_PRIVKEY "tls-privkey.pem" +#define TLS_PUBKEY "tls-pubkey.pem" #define HOOKDIR "/lib/mandos/network-hooks.d" bool debug = false; @@ -699,7 +707,6 @@ const char *dhparamsfilename, mandos_context *mc){ int ret; - unsigned int uret; if(debug){ fprintf_plus(stderr, "Initializing GnuTLS\n"); @@ -722,18 +729,34 @@ } if(debug){ - fprintf_plus(stderr, "Attempting to use OpenPGP public key %s and" - " secret key %s as GnuTLS credentials\n", + fprintf_plus(stderr, "Attempting to use public key %s and" + " private key %s as GnuTLS credentials\n", pubkeyfilename, seckeyfilename); } +#if GNUTLS_VERSION_NUMBER >= 0x030606 + ret = gnutls_certificate_set_rawpk_key_file + (mc->cred, pubkeyfilename, seckeyfilename, + GNUTLS_X509_FMT_PEM, /* format */ + NULL, /* pass */ + /* key_usage */ + GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT, + NULL, /* names */ + 0, /* names_length */ + /* privkey_flags */ + GNUTLS_PKCS_PLAIN | GNUTLS_PKCS_NULL_PASSWORD, + 0); /* pkcs11_flags */ +#elif GNUTLS_VERSION_NUMBER < 0x030600 ret = gnutls_certificate_set_openpgp_key_file (mc->cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); +#else +#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" +#endif if(ret != GNUTLS_E_SUCCESS){ fprintf_plus(stderr, - "Error[%d] while reading the OpenPGP key pair ('%s'," + "Error[%d] while reading the key pair ('%s'," " '%s')\n", ret, pubkeyfilename, seckeyfilename); fprintf_plus(stderr, "The GnuTLS error is: %s\n", safer_gnutls_strerror(ret)); @@ -810,6 +833,7 @@ } if(dhparamsfilename == NULL){ if(mc->dh_bits == 0){ +#if GNUTLS_VERSION_NUMBER < 0x030600 /* Find out the optimal number of DH bits */ /* Try to read the private key file */ gnutls_datum_t buffer = { .data = NULL, .size = 0 }; @@ -895,7 +919,7 @@ } } } - uret = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, sec_param); + unsigned int uret = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, sec_param); if(uret != 0){ mc->dh_bits = uret; if(debug){ @@ -913,19 +937,22 @@ safer_gnutls_strerror(ret)); goto globalfail; } - } else if(debug){ - fprintf_plus(stderr, "DH bits explicitly set to %u\n", - mc->dh_bits); - } - ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error in GnuTLS prime generation (%u" - " bits): %s\n", mc->dh_bits, - safer_gnutls_strerror(ret)); - goto globalfail; +#endif + } else { /* dh_bits != 0 */ + if(debug){ + fprintf_plus(stderr, "DH bits explicitly set to %u\n", + mc->dh_bits); + } + ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); + if(ret != GNUTLS_E_SUCCESS){ + fprintf_plus(stderr, "Error in GnuTLS prime generation (%u" + " bits): %s\n", mc->dh_bits, + safer_gnutls_strerror(ret)); + goto globalfail; + } + gnutls_certificate_set_dh_params(mc->cred, mc->dh_params); } } - gnutls_certificate_set_dh_params(mc->cred, mc->dh_params); return 0; @@ -942,7 +969,14 @@ int ret; /* GnuTLS session creation */ do { - ret = gnutls_init(session, GNUTLS_SERVER); + ret = gnutls_init(session, (GNUTLS_SERVER +#if GNUTLS_VERSION_NUMBER >= 0x030506 + | GNUTLS_NO_TICKETS +#endif +#if GNUTLS_VERSION_NUMBER >= 0x030606 + | GNUTLS_ENABLE_RAWPK +#endif + )); if(quit_now){ return -1; } @@ -2427,8 +2461,16 @@ int main(int argc, char *argv[]){ mandos_context mc = { .server = NULL, .dh_bits = 0, +#if GNUTLS_VERSION_NUMBER >= 0x030606 + .priority = "SECURE128:!CTYPE-X.509" + ":+CTYPE-RAWPK:!RSA:!VERS-ALL:+VERS-TLS1.3" + ":%PROFILE_ULTRA", +#elif GNUTLS_VERSION_NUMBER < 0x030600 .priority = "SECURE256:!CTYPE-X.509" ":+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256", +#else +#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" +#endif .current_server = NULL, .interfaces = NULL, .interfaces_size = 0 }; AvahiSServiceBrowser *sb = NULL; @@ -2445,6 +2487,10 @@ AvahiIfIndex if_index = AVAHI_IF_UNSPEC; const char *seckey = PATHDIR "/" SECKEY; const char *pubkey = PATHDIR "/" PUBKEY; +#if GNUTLS_VERSION_NUMBER >= 0x030606 + const char *tls_privkey = PATHDIR "/" TLS_PRIVKEY; + const char *tls_pubkey = PATHDIR "/" TLS_PUBKEY; +#endif const char *dh_params_file = NULL; char *interfaces_hooks = NULL; @@ -2498,7 +2544,23 @@ { .name = "pubkey", .key = 'p', .arg = "FILE", .doc = "OpenPGP public key file base name", - .group = 2 }, + .group = 1 }, + { .name = "tls-privkey", .key = 't', + .arg = "FILE", +#if GNUTLS_VERSION_NUMBER >= 0x030606 + .doc = "TLS private key file base name", +#else + .doc = "Dummy; ignored (requires GnuTLS 3.6.6)", +#endif + .group = 1 }, + { .name = "tls-pubkey", .key = 'T', + .arg = "FILE", +#if GNUTLS_VERSION_NUMBER >= 0x030606 + .doc = "TLS public key file base name", +#else + .doc = "Dummy; ignored (requires GnuTLS 3.6.6)", +#endif + .group = 1 }, { .name = "dh-bits", .key = 129, .arg = "BITS", .doc = "Bit length of the prime number used in the" @@ -2560,6 +2622,16 @@ case 'p': /* --pubkey */ pubkey = arg; break; + case 't': /* --tls-privkey */ +#if GNUTLS_VERSION_NUMBER >= 0x030606 + tls_privkey = arg; +#endif + break; + case 'T': /* --tls-pubkey */ +#if GNUTLS_VERSION_NUMBER >= 0x030606 + tls_pubkey = arg; +#endif + break; case 129: /* --dh-bits */ errno = 0; tmpmax = strtoimax(arg, &tmp, 10); @@ -2919,7 +2991,13 @@ goto end; } +#if GNUTLS_VERSION_NUMBER >= 0x030606 + ret = init_gnutls_global(tls_pubkey, tls_privkey, dh_params_file, &mc); +#elif GNUTLS_VERSION_NUMBER < 0x030600 ret = init_gnutls_global(pubkey, seckey, dh_params_file, &mc); +#else +#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" +#endif if(ret == -1){ fprintf_plus(stderr, "init_gnutls_global failed\n"); exitcode = EX_UNAVAILABLE; === modified file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/mandos-client.xml 2019-02-09 23:23:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -95,6 +95,20 @@ FILE + + + + + + + + + + @@ -154,10 +168,10 @@ brings up network interfaces, uses the interfaces’ IPv6 link-local addresses to get network connectivity, uses Zeroconf to find servers on the local network, and communicates with - servers using TLS with an OpenPGP key to ensure authenticity and - confidentiality. This client program keeps running, trying all - servers on the network, until it receives a satisfactory reply - or a TERM signal. After all servers have been tried, all + servers using TLS with a raw public key to ensure authenticity + and confidentiality. This client program keeps running, trying + all servers on the network, until it receives a satisfactory + reply or a TERM signal. After all servers have been tried, all servers are periodically retried. If no servers are found it will wait indefinitely for new servers to appear. @@ -307,6 +321,34 @@ + + + + + TLS raw public key file name. The default name is + /conf/conf.d/mandos/tls-pubkey.pem. + + + + + + + + + + TLS secret key file name. The default name is + /conf/conf.d/mandos/tls-privkey.pem. + + + + + @@ -322,9 +364,10 @@ Sets the number of bits to use for the prime number in the TLS Diffie-Hellman key exchange. The default value is - selected automatically based on the OpenPGP key. Note - that if the option is used, - the values from that file will be used instead. + selected automatically based on the GnuTLS security + profile set in its priority string. Note that if the + option is used, the values + from that file will be used instead. @@ -682,6 +725,20 @@ + /conf/conf.d/mandos/tls-pubkey.pem + /conf/conf.d/mandos/tls-privkey.pem + + + Public and private raw key files, in PEM + format. These are the default file names, they can be + changed with the and + options. + + + + /lib/mandos/network-hooks.d @@ -729,18 +786,18 @@ - Run in debug mode, and use a custom key: + Run in debug mode, and use custom keys: -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt +&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --tls-pubkey keydir/tls-pubkey.pem --tls-privkey keydir/tls-privkey.pem - Run in debug mode, with a custom key, and do not use Zeroconf + Run in debug mode, with custom keys, and do not use Zeroconf to locate a server; connect directly to the IPv6 link-local address fe80::aede:48ff:fe71:f6f2, port 4711, @@ -749,7 +806,7 @@ -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 +&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --tls-pubkey keydir/tls-pubkey.pem --tls-privkey keydir/tls-privkey.pem --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 @@ -779,12 +836,12 @@ The only remaining weak point is that someone with physical access to the client hard drive might turn off the client - computer, read the OpenPGP keys directly from the hard drive, - and communicate with the server. To safeguard against this, the - server is supposed to notice the client disappearing and stop - giving out the encrypted data. Therefore, it is important to - set the timeout and checker interval values tightly on the - server. See mandos8. @@ -850,7 +907,7 @@ GnuTLS is the library this client uses to implement TLS for communicating securely with the server, and at the same time - send the public OpenPGP key to the server. + send the public key to the server. @@ -922,13 +979,28 @@ + RFC 7250: Using Raw Public Keys in Transport + Layer Security (TLS) and Datagram Transport Layer Security + (DTLS) + + + + This is implemented by GnuTLS in version 3.6.6 and is, if + present, used by this program so that raw public keys can be + used. + + + + + RFC 6091: Using OpenPGP Keys for Transport Layer Security - This is implemented by GnuTLS and used by this program so - that OpenPGP keys can be used. + This is implemented by GnuTLS before version 3.6.0 and is, + if present, used by this program so that OpenPGP keys can be + used.