=== modified file 'DBUS-API' --- DBUS-API 2018-02-08 10:23:55 +0000 +++ DBUS-API 2019-02-10 04:20: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 | @@ -130,8 +131,8 @@ * Copyright - Copyright © 2010-2018 Teddy Hogeborn - Copyright © 2010-2018 Björn Påhlsson + Copyright © 2010-2019 Teddy Hogeborn + Copyright © 2010-2019 Björn Påhlsson ** License: === 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 'TODO' --- TODO 2017-02-22 21:45:35 +0000 +++ TODO 2019-02-09 23:31:44 +0000 @@ -119,16 +119,5 @@ * [[http://www.undeadly.org/cgi?action=article&sid=20110530221728][OpenBSD]] -* TODO Use raw public keys (RFC 7250) for TLS communications :2: -** Support for this is planned for GnuTLS version 3.6 - https://gitlab.com/gnutls/gnutls/issues/26 -** Rationale -*** The client key is used both for communication and encryption - Using raw keys in GnuTLS instead uses separate keys for - communication and password decryption. -*** GnuTLS 3.5.9 has deprecated the OpenPGP functions - The functions are still available, but deprecated: - https://gitlab.com/gnutls/gnutls/issues/102 - #+STARTUP: showall === modified file 'clients.conf' --- clients.conf 2012-06-23 00:58:49 +0000 +++ clients.conf 2019-02-09 23:34:15 +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,11 +71,14 @@ ;#### ;# 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 ; ;# If "secret" is not specified, a file can be read for the data. -;secfile = /etc/mandos/bar-secret.bin +;secfile = /etc/keys/mandos/bar-secret.bin ; ;# An IP address for host is also fine, if the checker accepts it. ;host = 192.0.2.3 === modified file 'debian/control' --- debian/control 2018-08-19 16:15:46 +0000 +++ debian/control 2019-02-10 03:50:20 +0000 @@ -7,11 +7,12 @@ 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 -Standards-Version: 4.2.0 +Standards-Version: 4.3.0 Vcs-Bzr: https://ftp.recompile.se/pub/mandos/trunk Vcs-Browser: https://bzr.recompile.se/loggerhead/mandos/trunk/files Homepage: https://www.recompile.se/mandos @@ -21,9 +22,11 @@ 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) + gnupg2 | gnupg, systemd-sysv | lsb-base (>= 3.0-6), + debconf (>= 1.5.5) | debconf-2.0 Recommends: ssh-client | fping Description: server giving encrypted passwords to Mandos clients This is the server part of the Mandos system, which allows @@ -33,18 +36,21 @@ 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. Package: mandos-client Architecture: linux-any -Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup, - initramfs-tools (>= 0.99), dpkg-dev (>=1.16.0) -Recommends: ssh, gnutls-bin | openssl +Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, + cryptsetup (<< 2:2.0.3-1) | cryptsetup-initramfs, + initramfs-tools (>= 0.99), dpkg-dev (>=1.16.0), + gnutls-bin (>= 3.6.6) | openssl (>= 1.1.0), + debconf (>= 1.5.5) | debconf-2.0 +Recommends: ssh Breaks: dropbear (<= 0.53.1-1) Enhances: cryptsetup Description: do unattended reboots with an encrypted root file system @@ -55,9 +61,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/copyright' --- debian/copyright 2018-02-08 10:23:55 +0000 +++ debian/copyright 2019-02-10 04:20:26 +0000 @@ -4,8 +4,8 @@ Source: Files: * -Copyright: Copyright © 2008-2018 Teddy Hogeborn - Copyright © 2008-2018 Björn Påhlsson +Copyright: Copyright © 2008-2019 Teddy Hogeborn + Copyright © 2008-2019 Björn Påhlsson License: GPL-3+ This file is part of Mandos. . === 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.lintian-overrides' --- debian/mandos-client.lintian-overrides 2016-03-19 04:21:00 +0000 +++ debian/mandos-client.lintian-overrides 2019-02-10 03:50:20 +0000 @@ -30,3 +30,14 @@ mandos-client binary: non-standard-dir-perm etc/mandos/plugins.d/ 0700 != 0755 # Likewise for plugin-helpers directory mandos-client binary: non-standard-dir-perm etc/mandos/plugin-helpers/ 0700 != 0755 + +# The debconf templates is only used for displaying information +# detected in the postinst, not for saving answers to questions, so we +# don't need a .config file. +mandos-client binary: no-debconf-config + +# The notice displayed from the postinst script really is critical +mandos-client binary: postinst-uses-db-input + +# It is a really long line +mandos-client binary: manpage-has-errors-from-man usr/share/man/man8/plugin-runner.8mandos.gz *: warning *: can't break line === modified file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2016-10-09 22:35:41 +0000 +++ debian/mandos-client.postinst 2019-02-10 03:50:20 +0000 @@ -15,6 +15,8 @@ # If prerm fails during replacement due to conflict: # abort-remove in-favour +. /usr/share/debconf/confmodule + set -e # Update the initial RAM file system image @@ -50,14 +52,68 @@ 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 + + key_id=$(mandos-keygen --passfile=/dev/null \ + | grep --regexp="^key_id[ =]") + + db_version 2.0 + db_fset mandos-client/key_id seen false + db_reset mandos-client/key_id + db_subst mandos-client/key_id key_id $key_id + db_input critical mandos-client/key_id || true + db_go + db_stop } create_dh_params(){ @@ -88,7 +144,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 ;; === added file 'debian/mandos-client.templates' --- debian/mandos-client.templates 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.templates 2019-02-10 03:50:20 +0000 @@ -0,0 +1,10 @@ +Template: mandos-client/key_id +Type: note +Description: New client option "${key_id}" is REQUIRED on server + A new "key_id" client option is REQUIRED in the server's clients.conf file, otherwise this computer most likely will not reboot unattended. This option: + . + ${key_id} + . + must be added (all on one line!) on the Mandos server host, in the file /etc/mandos/clients.conf, right before the "fingerprint" option for this Mandos client. You must edit that file on that server and add this option. + . + With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS session keys. A new TLS key pair has been generated and will be used as identification, but the key ID of the public key needs to be added to the server, since this will now be used to identify the client to the server. === modified file 'debian/mandos.lintian-overrides' --- debian/mandos.lintian-overrides 2018-02-10 18:58:32 +0000 +++ debian/mandos.lintian-overrides 2019-02-10 03:50:20 +0000 @@ -3,3 +3,11 @@ # mandos binary: non-standard-file-perm etc/mandos/clients.conf 0600 != 0644 mandos: init.d-script-needs-depends-on-lsb-base etc/init.d/mandos (line 46) + +# The debconf templates is only used for displaying information +# detected in the postinst, not for saving answers to questions, so we +# don't need a .config file. +mandos binary: no-debconf-config + +# The notice displayed from the postinst script really is critical +mandos binary: postinst-uses-db-input === modified file 'debian/mandos.postinst' --- debian/mandos.postinst 2016-03-19 03:48:56 +0000 +++ debian/mandos.postinst 2019-02-10 03:50:20 +0000 @@ -15,6 +15,8 @@ # If prerm fails during replacement due to conflict: # abort-remove in-favour +. /usr/share/debconf/confmodule + set -e case "$1" in @@ -53,6 +55,17 @@ chown _mandos:_mandos /var/lib/mandos chmod u=rwx,go= /var/lib/mandos fi + + gnutls_version=$(dpkg-query --showformat='${Version}' \ + --show libgnutls30 \ + 2>/dev/null || :) + if [ -n "$gnutls_version" ] \ + && dpkg --compare-versions $gnutls_version ge 3.6.6; then + db_version 2.0 + db_input critical mandos/key_id || true + db_go + db_stop + fi ;; abort-upgrade|abort-deconfigure|abort-remove) === added file 'debian/mandos.templates' --- debian/mandos.templates 1970-01-01 00:00:00 +0000 +++ debian/mandos.templates 2019-02-10 03:50:20 +0000 @@ -0,0 +1,14 @@ +Template: mandos/key_id +Type: note +Description: New client option "key_id" is REQUIRED on server + A new "key_id" client option is REQUIRED in the clients.conf file, otherwise the client most likely will not reboot unattended. This option: + . + key_id = + . + must be added in the file /etc/mandos/clients.conf, right before the "fingerprint" option, for each Mandos client. You must edit that file and add this option for all clients. To see the correct key ID for each client, run this command (on each client): + . + mandos-keygen -F/dev/null|grep ^key_id + . + Note: the client must all also be using GnuTLS 3.6.6 or later; the server cannot serve passwords for both old and new clients! + . + Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS session keys. A new TLS key pair will be generated on each client and will be used as identification, but the key ID of the public key needs to be added to this server, since this will now be used to identify the client to the server. === modified file 'intro.xml' --- intro.xml 2018-02-08 10:23:55 +0000 +++ intro.xml 2019-02-10 04:20:26 +0000 @@ -1,7 +1,7 @@ + %common; ]> @@ -38,6 +38,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson @@ -67,10 +68,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 +201,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-10 04:20:26 +0000 @@ -11,8 +11,8 @@ # "AvahiService" class, and some lines in "main". # # Everything else is -# Copyright © 2008-2018 Teddy Hogeborn -# Copyright © 2008-2018 Björn Påhlsson +# Copyright © 2008-2019 Teddy Hogeborn +# Copyright © 2008-2019 Björn Påhlsson # # This file is part of Mandos. # @@ -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-10 04:20:26 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -43,6 +43,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson @@ -186,9 +187,9 @@ >-- %%(host)s. Note that mandos-keygen, when generating output to be inserted into this file, normally looks for an SSH - server on the Mandos client, and, if it find one, outputs + server on the Mandos client, and, if it finds one, outputs a option to check for the - client’s key fingerprint – this is more secure against + client’s SSH key fingerprint – this is more secure against spoofing. @@ -239,6 +240,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 +331,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 +434,7 @@ created, enabled, expires, + key_id, fingerprint, host, interval, @@ -479,6 +497,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 +520,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-10 04:20:26 +0000 @@ -3,8 +3,8 @@ # # Mandos Monitor - Control and monitor the Mandos server # -# Copyright © 2008-2018 Teddy Hogeborn -# Copyright © 2008-2018 Björn Påhlsson +# Copyright © 2008-2019 Teddy Hogeborn +# Copyright © 2008-2019 Björn Påhlsson # # This file is part of Mandos. # @@ -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-ctl.xml' --- mandos-ctl.xml 2018-02-08 10:23:55 +0000 +++ mandos-ctl.xml 2019-02-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,6 +40,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson === modified file 'mandos-keygen' --- mandos-keygen 2018-08-19 20:17:48 +0000 +++ mandos-keygen 2019-02-10 04:20:26 +0000 @@ -1,9 +1,9 @@ #!/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 +# Copyright © 2008-2019 Teddy Hogeborn +# Copyright © 2008-2019 Björn Påhlsson # # This file is part of Mandos. # @@ -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-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,6 +42,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson @@ -127,6 +128,13 @@ + + + + + @@ -180,12 +188,12 @@ DESCRIPTION &COMMANDNAME; is a program to generate the - OpenPGP key used by + TLS and OpenPGP keys used by mandos-client - 8mandos. The key is - 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. + 8mandos. The keys are + normally written to /etc/keys/mandos for later installation into + the initrd image, but this, and most other things, can be + changed with command line options. This program can also be used with the @@ -228,8 +236,8 @@ DIRECTORY - Target directory for key files. Default is - /etc/mandos. + Target directory for key files. Default is /etc/keys/mandos. @@ -241,7 +249,7 @@ TYPE - Key type. Default is RSA. + OpenPGP key type. Default is RSA. @@ -253,7 +261,7 @@ BITS - Key length in bits. Default is 4096. + OpenPGP key length in bits. Default is 4096. @@ -265,8 +273,7 @@ KEYTYPE - Subkey type. Default is RSA (Elgamal - encryption-only). + OpenPGP subkey type. Default is RSA @@ -278,7 +285,7 @@ BITS - Subkey length in bits. Default is 4096. + OpenPGP subkey length in bits. Default is 4096. @@ -322,6 +329,18 @@ + + + + + TLS key type. Default is ed25519 + + + + + @@ -336,8 +355,8 @@ Prompt for a password and encrypt it with the key already - present in either /etc/mandos or the - directory specified with the + present in either /etc/keys/mandos or + the directory specified with the option. Outputs, on standard output, a section suitable for inclusion in mandos-clients.confOVERVIEW - 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. @@ -423,7 +442,7 @@ - /etc/mandos/seckey.txt + /etc/keys/mandos/seckey.txt OpenPGP secret key file which will be created or @@ -432,7 +451,7 @@ - /etc/mandos/pubkey.txt + /etc/keys/mandos/pubkey.txt OpenPGP public key file which will be created or @@ -441,6 +460,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 @@ -481,9 +516,9 @@ - Prompt for a password, encrypt it with the key in /etc/mandos and output a section - suitable for clients.conf. + Prompt for a password, encrypt it with the keys in /etc/keys/mandos and output a + section suitable for clients.conf. &COMMANDNAME; --password @@ -491,7 +526,7 @@ - Prompt for a password, encrypt it with the key in the + Prompt for a password, encrypt it with the keys in the client-key directory and output a section suitable for clients.conf. === modified file 'mandos-monitor' --- mandos-monitor 2018-08-19 20:17:48 +0000 +++ mandos-monitor 2019-02-10 04:20:26 +0000 @@ -3,8 +3,8 @@ # # Mandos Monitor - Control and monitor the Mandos server # -# Copyright © 2009-2018 Teddy Hogeborn -# Copyright © 2009-2018 Björn Påhlsson +# Copyright © 2009-2019 Teddy Hogeborn +# Copyright © 2009-2019 Björn Påhlsson # # This file is part of Mandos. # @@ -444,7 +444,7 @@ self.clients_dict = {} # We will add Text widgets to this list - self.log = [] + self.log = urwid.SimpleListWalker([]) self.max_log_length = max_log_length self.log_level = log_level @@ -503,7 +503,7 @@ if self.max_log_length: if len(self.log) > self.max_log_length: del self.log[0:len(self.log)-self.max_log_length-1] - self.logbox.set_focus(len(self.logbox.body.contents), + self.logbox.set_focus(len(self.logbox.body.contents)-1, coming_from="above") self.refresh() === modified file 'mandos-monitor.xml' --- mandos-monitor.xml 2018-02-08 10:23:55 +0000 +++ mandos-monitor.xml 2019-02-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,6 +40,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson === 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.conf.xml' --- mandos.conf.xml 2018-02-08 10:23:55 +0000 +++ mandos.conf.xml 2019-02-10 04:20:26 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/mandos.conf"> - + %common; ]> @@ -43,6 +43,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson === modified file 'mandos.xml' --- mandos.xml 2018-02-08 10:23:55 +0000 +++ mandos.xml 2019-02-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,6 +42,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson @@ -361,11 +362,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 +392,7 @@ - OpenPGP public key (part of TLS handshake) + Public key (part of TLS handshake) -> @@ -586,10 +587,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 +643,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 +712,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 +771,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-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,6 +42,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson @@ -634,7 +635,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/askpass-fifo.xml' --- plugins.d/askpass-fifo.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/askpass-fifo.xml 2019-02-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,6 +42,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson === 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-10 04:20:26 +0000 @@ -9,8 +9,8 @@ * "browse_callback", and parts of "main". * * Everything else is - * Copyright © 2008-2018 Teddy Hogeborn - * Copyright © 2008-2018 Björn Påhlsson + * Copyright © 2008-2019 Teddy Hogeborn + * Copyright © 2008-2019 Björn Påhlsson * * This file is part of Mandos. * @@ -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-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,6 +42,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson @@ -95,6 +96,20 @@ FILE + + + + + + + + + + @@ -154,10 +169,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 +322,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 +365,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 +726,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 +787,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 +807,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 +837,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 +908,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 +980,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. === modified file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/password-prompt.xml 2019-02-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,6 +42,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/plymouth.xml' --- plugins.d/plymouth.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/plymouth.xml 2019-02-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -40,6 +40,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/splashy.xml' --- plugins.d/splashy.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/splashy.xml 2019-02-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,6 +42,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson === modified file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 2018-02-08 10:23:55 +0000 +++ plugins.d/usplash.xml 2019-02-10 04:20:26 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -42,6 +42,7 @@ 2016 2017 2018 + 2019 Teddy Hogeborn Björn Påhlsson