>From 0070321f127169262e1f562734485e495e041e32 Mon Sep 17 00:00:00 2001 From: Amin Bandali Date: Wed, 14 Apr 2021 23:32:54 -0400 Subject: [PATCH] Add support for using a TLS client certificate with 'erc-tls' * lisp/erc/erc-backend.el (erc-session-client-certificate): New buffer-local variable storing the TLS client certificate used for the current connection. (erc-open-network-stream): Use open-network-stream instead of make-network-process, and pass any additional arguments to it. (erc-server-connect): Add an optional client-certificate argument that if present is passed with the :client-certificate keyword as part of the arguments to erc-server-connect-function. * lisp/erc/erc.el (erc-open): Add new optional client-certificate argument, set it as erc-session-client-certificate, and pass it along to erc-server-connect. (erc-tls): Add new client-certificate keyword argument and pass it in the direct call to erc-open (instead of going through erc). (erc-open-tls-stream): Pass any additional arguments (such as :client-certificate) to open-network-stream. Also allow overriding :nowait if desired. * doc/misc/erc.texi: Add documentation for erc-tls, including the new :client-certificate argument. * etc/NEWS: Announce the change. --- doc/misc/erc.texi | 66 +++++++++++++++++++++++++++++ etc/NEWS | 36 ++++++++++++++++ lisp/erc/erc-backend.el | 30 +++++++++---- lisp/erc/erc.el | 94 +++++++++++++++++++++++++++++++++-------- 4 files changed, 200 insertions(+), 26 deletions(-) diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index d635cac5ab..0620f18096 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -523,6 +523,72 @@ Connecting @end example @end defun +To connect securely over an encrypted TLS connection, use @kbd{M-x +erc-tls}. + +@defun erc-tls +Select connection parameters and run ERC over TLS@. +Non-interactively, it takes the following keyword arguments. + +@itemize @bullet +@item @var{server} +@item @var{port} +@item @var{nick} +@item @var{password} +@item @var{full-name} +@item @var{client-certificate} +@end itemize + +That is, if called with the following arguments, @var{server} and +@var{full-name} will be set to those values, whereas +@code{erc-compute-port}, @code{erc-compute-nick} and +@code{erc-compute-full-name} will be invoked for the values of the +other parameters, and @code{client-certificate} will be @code{nil}. + +@example +(erc-tls :server "chat.freenode.net" :full-name "Harry S Truman") +@end example + +To use a certificate with @code{erc-tls}, specify the optional +@var{client-certificate} keyword argument, whose value should be +as described in the documentation of @code{open-network-stream}: if +non-@code{nil}, it should either be a list where the first element is +the file name of the private key corresponding to a client certificate +and the second element is the file name of the client certificate +itself to use when connecting over TLS, or @code{t}, which means that +@code{auth-source} will be queried for the private key and the +certificate. + +Examples of use: + +@example + (erc-tls :server "chat.freenode.net" :port 6697 + :client-certificate + '("/home/bandali/my-cert.key" + "/home/bandali/my-cert.crt")) +@end example + +@example + (erc-tls :server "chat.freenode.net" :port 6697 + :client-certificate + `(,(expand-file-name "~/cert-freenode.key") + ,(expand-file-name "~/cert-freenode.crt"))) +@end example + +@example + (erc-tls :server "chat.freenode.net" :port 6697 + :client-certificate t) +@end example + +In the case of @code{:client-certificate t}, you will need to add a +line like the following to your authinfo file +(e.g. @file{~/.authinfo.gpg}): + +@example +machine chat.freenode.net key /home/bandali/my-cert.key cert /home/bandali/my-cert.crt +@end example +@end defun + @subheading Server @defun erc-compute-server &optional server diff --git a/etc/NEWS b/etc/NEWS index b641e8d95f..22a9243ae2 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1731,6 +1731,42 @@ type for highlighting the entire message but not the sender's nick. The 'erc-status-sidebar' package which provides a HexChat-like activity overview sidebar for joined IRC channels is now part of ERC. ++++ +*** erc-tls now supports specifying a TLS client certificate. +The 'erc-tls' function has been updated to allow specifying a TLS +client certificate for authentication, as an alternative to NickServ +password-based authentication. This is referred to as "CertFP" (short +for Certificate Fingerprint) by several IRC networks. + +To use a certificate with 'erc-tls', specify the ':client-certificate' +optional parameter, whose value should be as described in the +documentation of 'open-network-stream': if non-nil, it should either +be a list where the first element is the file name of the private key +corresponding to a client certificate and the second element is the +file name of the client certificate itself to use when connecting over +TLS, or t, which means that 'auth-source' will be queried for the +private key and the certificate. + +Examples of use: + + (erc-tls :server "chat.freenode.net" :port 6697 + :client-certificate + '("/home/bandali/my-cert.key" + "/home/bandali/my-cert.crt")) + + (erc-tls :server "chat.freenode.net" :port 6697 + :client-certificate + `(,(expand-file-name "~/cert-freenode.key") + ,(expand-file-name "~/cert-freenode.crt"))) + + (erc-tls :server "chat.freenode.net" :port 6697 + :client-certificate t) + +In the case of ':client-certificate t', you will need to add a line +like the following to your authinfo file (e.g. "~/.authinfo.gpg"): + + machine chat.freenode.net key /home/bandali/my-cert.key cert /home/bandali/my-cert.crt + ** Battery --- diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index b1f97aea06..67db572701 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -138,6 +138,13 @@ erc-session-connector (defvar-local erc-session-port nil "The port used to connect to.") +(defvar-local erc-session-client-certificate nil + "TLS client certificate used when connecting over TLS. +If non-nil, should either be a list where the first element is +the certificate key file name, and the second element is the +certificate file name itself, or t, which means that +`auth-source' will be queried for the key and the certificate.") + (defvar-local erc-server-announced-name nil "The name the server announced to use.") @@ -505,18 +512,23 @@ erc-server-process-alive (memq (process-status erc-server-process) '(run open))))) ;;;; Connecting to a server -(defun erc-open-network-stream (name buffer host service) - "As `open-network-stream', but does non-blocking IO" - (make-network-process :name name :buffer buffer - :host host :service service :nowait t)) +(defun erc-open-network-stream (name buffer host service &rest parameters) + "Like `open-network-stream', but does non-blocking IO." + (let ((p (plist-put parameters :nowait t))) + (open-network-stream name buffer host service p))) -(defun erc-server-connect (server port buffer) +(defun erc-server-connect (server port buffer &optional client-certificate) "Perform the connection and login using the specified SERVER and PORT. -We will store server variables in the buffer given by BUFFER." - (let ((msg (erc-format-message 'connect ?S server ?p port)) process) +We will store server variables in the buffer given by BUFFER. +CLIENT-CERTIFICATE may optionally be used to specify a TLS client +certificate to use for authentication when connecting over +TLS (see `erc-session-client-certificate' for more details)." + (let ((msg (erc-format-message 'connect ?S server ?p port)) process + (args `(,(format "erc-%s-%s" server port) nil ,server ,port))) + (when client-certificate + (setq args `(,@args :client-certificate ,client-certificate))) (message "%s" msg) - (setq process (funcall erc-server-connect-function - (format "erc-%s-%s" server port) nil server port)) + (setq process (apply erc-server-connect-function args)) (unless (processp process) (error "Connection attempt failed")) ;; Misc server variables diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index e20aa8057d..c97f6d4342 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -47,8 +47,12 @@ ;; ;; M-x erc RET ;; -;; After you are connected to a server, you can use C-h m or have a look at -;; the ERC menu. +;; or +;; +;; M-x erc-tls RET +;; +;; to connect over TLS (encrypted). Once you are connected to a +;; server, you can use C-h m or have a look at the ERC menu. ;;; Code: @@ -1967,7 +1971,8 @@ erc-setup-buffer (switch-to-buffer buffer))))) (defun erc-open (&optional server port nick full-name - connect passwd tgt-list channel process) + connect passwd tgt-list channel process + client-certificate) "Connect to SERVER on PORT as NICK with FULL-NAME. If CONNECT is non-nil, connect to the server. Otherwise assume @@ -1977,6 +1982,13 @@ erc-open Use PASSWD as user password on the server. If TGT-LIST is non-nil, use it to initialize `erc-default-recipients'. +CLIENT-CERTIFICATE, if non-nil, should either be a list where the +first element is the file name of the private key corresponding +to a client certificate and the second element is the file name +of the client certificate itself to use when connecting over TLS, +or t, which means that `auth-source' will be queried for the +private key and the certificate. + Returns the buffer for the given server or channel." (let ((server-announced-name (when (and (boundp 'erc-session-server) (string= server erc-session-server)) @@ -2059,6 +2071,8 @@ erc-open (if (functionp secret) (funcall secret) secret)))) + ;; client certificate (only useful if connecting over TLS) + (setq erc-session-client-certificate client-certificate) ;; debug output buffer (setq erc-dbuf (when erc-log-p @@ -2079,7 +2093,10 @@ erc-open (run-hook-with-args 'erc-connect-pre-hook buffer) (when connect - (erc-server-connect erc-session-server erc-session-port buffer)) + (erc-server-connect erc-session-server + erc-session-port + buffer + erc-session-client-certificate)) (erc-update-mode-line) ;; Now display the buffer in a window as per user wishes. @@ -2203,15 +2220,15 @@ erc (port (erc-compute-port)) (nick (erc-compute-nick)) password - (full-name (erc-compute-full-name))) + (full-name (erc-compute-full-name)) That is, if called with (erc :server \"chat.freenode.net\" :full-name \"Harry S Truman\") then the server and full-name will be set to those values, whereas -`erc-compute-port', `erc-compute-nick' and `erc-compute-full-name' will -be invoked for the values of the other parameters." +`erc-compute-port', `erc-compute-nick' and `erc-compute-full-name' +will be invoked for the values of the other parameters." (interactive (erc-select-read-args)) (erc-open server port nick full-name t password)) @@ -2220,21 +2237,64 @@ 'erc-select (defalias 'erc-ssl #'erc-tls) ;;;###autoload -(defun erc-tls (&rest r) - "Interactively select TLS connection parameters and run ERC. -Arguments are the same as for `erc'." +(cl-defun erc-tls (&key (server (erc-compute-server)) + (port (erc-compute-port)) + (nick (erc-compute-nick)) + password + (full-name (erc-compute-full-name)) + client-certificate) + "ERC is a powerful, modular, and extensible IRC client. +This function is the main entry point for ERC over TLS. + +It allows you to select connection parameters, and then starts +ERC over TLS. + +Non-interactively, it takes the keyword arguments + (server (erc-compute-server)) + (port (erc-compute-port)) + (nick (erc-compute-nick)) + password + (full-name (erc-compute-full-name)) + client-certificate + +That is, if called with + + (erc-tls :server \"chat.freenode.net\" :full-name \"Harry S Truman\") + +then the server and full-name will be set to those values, whereas +`erc-compute-port', `erc-compute-nick', and `erc-compute-full-name' +will be invoked for the values of their respective parameters. + +CLIENT-CERTIFICATE, if non-nil, should either be a list where the +first element is the certificate key file name, and the second +element is the certificate file name itself, or t, which means +that `auth-source' will be queried for the key and the +certificate. + +Example usage: + + (erc-tls :server \"chat.freenode.net\" :port 6697 + :client-certificate + '(\"/data/bandali/my-cert.key\" + \"/data/bandali/my-cert.crt\"))" (interactive (let ((erc-default-port erc-default-port-tls)) (erc-select-read-args))) (let ((erc-server-connect-function 'erc-open-tls-stream)) - (apply #'erc r))) + (erc-open server port nick full-name t password + nil nil nil client-certificate))) -(defun erc-open-tls-stream (name buffer host port) +(defun erc-open-tls-stream (name buffer host port &rest parameters) "Open an TLS stream to an IRC server. -The process will be given the name NAME, its target buffer will be -BUFFER. HOST and PORT specify the connection target." - (open-network-stream name buffer host port - :nowait t - :type 'tls)) +The process will be given the name NAME, its target buffer will +be BUFFER. HOST and PORT specify the connection target. +PARAMETERS should be a sequence of keywords and values, per +`open-network-stream'." + (let ((p (plist-put parameters :type 'tls)) + args) + (unless (plist-member p :nowait) + (setq p (plist-put p :nowait t))) + (setq args `(,name ,buffer ,host ,port ,@p)) + (apply #'open-network-stream args))) ;;; Displaying error messages -- 2.17.1