From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Amin Bandali Newsgroups: gmane.emacs.bugs Subject: bug#47788: Add support for TLS client certificates to 'erc-tls' Date: Thu, 22 Apr 2021 20:45:29 -0400 Message-ID: <871rb1zv4m.fsf__31420.3095551179$1619138780$gmane$org@gnu.org> References: <87fszsuqqh.fsf@gnu.org> <87zgxzn2ek.fsf@neverwas.me> <87h7k3mm6x.fsf@gnu.org> <87h7k1ysje.fsf@neverwas.me> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="24799"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) Cc: Robert Pluim , 47788-done@debbugs.gnu.org, emacs-erc@gnu.org To: "J.P." Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Fri Apr 23 02:46:16 2021 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lZjxX-0006Lv-Nt for geb-bug-gnu-emacs@m.gmane-mx.org; Fri, 23 Apr 2021 02:46:15 +0200 Original-Received: from localhost ([::1]:39524 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lZjxW-00027n-RB for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 22 Apr 2021 20:46:14 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:52998) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lZjxK-00026Y-UE for bug-gnu-emacs@gnu.org; Thu, 22 Apr 2021 20:46:03 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]:52552) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1lZjxK-0002xo-Gn for bug-gnu-emacs@gnu.org; Thu, 22 Apr 2021 20:46:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1lZjxK-0005B9-FH for bug-gnu-emacs@gnu.org; Thu, 22 Apr 2021 20:46:02 -0400 Resent-From: Amin Bandali Original-Sender: "Debbugs-submit" Resent-To: bug-gnu-emacs@gnu.org Resent-Date: Fri, 23 Apr 2021 00:46:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: cc-closed 47788 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Mail-Followup-To: 47788@debbugs.gnu.org, bandali@gnu.org, bandali@gnu.org Original-Received: via spool by 47788-done@debbugs.gnu.org id=D47788.161913874119869 (code D ref 47788); Fri, 23 Apr 2021 00:46:02 +0000 Original-Received: (at 47788-done) by debbugs.gnu.org; 23 Apr 2021 00:45:41 +0000 Original-Received: from localhost ([127.0.0.1]:35864 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1lZjwy-0005AP-1x for submit@debbugs.gnu.org; Thu, 22 Apr 2021 20:45:40 -0400 Original-Received: from eggs.gnu.org ([209.51.188.92]:40334) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1lZjwu-0005A8-Ey for 47788-done@debbugs.gnu.org; Thu, 22 Apr 2021 20:45:38 -0400 Original-Received: from fencepost.gnu.org ([2001:470:142:3::e]:59740) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lZjwo-0002dg-Pg; Thu, 22 Apr 2021 20:45:30 -0400 Original-Received: from [2607:fea8:3fdf:eb8d:e45c:4581:b27:2cd5] (port=45082 helo=localhost) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1lZjwo-0001AI-G7; Thu, 22 Apr 2021 20:45:30 -0400 In-Reply-To: <87h7k1ysje.fsf@neverwas.me> X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:204728 Archived-At: --=-=-= Content-Type: text/plain J.P. writes: > Amin Bandali writes: > >>> It reconnected successfully with no hiccups, so I think that's one >>> for the win column. > > This continues to be the case with the latest changes applied; ditto > when using the authinfo variant of the new :client-certificate param. Great! >>> Beyond that, users may appreciate a mention of the new additions in the >>> info manual and maybe the wiki as well (instead of just NEWS). >> >> Certainly; done in v2. > > Nice job. This sort of thing can be a real time sink (at least for me). Thanks, and agreed; but it's good to take the time and do it anyway. :-) > One question. And stop me if I've confused myself here, but the doc > string and the tex-info section for `erc-tls' both offer the same > example (borrowed from their plain `erc' counterparts) in which the > function `erc-compute-full-name' is said to be consulted despite the > :full-name "Harry S. Truman" keyword arg being present. While it's > (technically) true that `erc-compute-full-name' always runs, I suspect > its inclusion in the original example was inadvertent (or something > about it has changed since 2007). Would it perhaps make sense to omit > `erc-compute-full-name' from the Truman example? Yeah that's fair! I've tried to clarify/remedy that in the latest revision. [...] > > Thanks, but I shouldn't have proposed that tweak without a concrete use > case in mind. In fact, I've only ever needed :nowait to be nil when > using a custom opener. If my suggestion made things more confusing or > distracting, my apologies. Perhaps it's best to just revert to what you > had originally or even just force it to t. (Not that you should waste > another second on this relative nothingburger.) No worries :-). And per our short discussion on #erc the other day, there could be a legitimate use-case for that. So I think it's safe to say that all in all it was a net positive change. >> Thanks for the suggestion. After some thought, I decided to change the >> interface of erc-tls from the two separate :client-key and :client-crt >> arguments in v1 of the patch to just one :client-certificate in v2, >> matching that of `open-network-stream'. I tested the change with >> `:client-certificate t' and confirm that it works fine for me. > > The interface change is a welcome improvement, IMO. Although dealing > with a list may feel slightly more cumbersome to some, the multiple > helpful examples make that a nonissue. (And sparing contributors an > extra param to worry about is a nice bonus as well.) But I guess the > real win is in accommodating the authinfo facility, which seems a rather > obvious and essential inclusion in retrospect. Great work! > Awesome. Yeah I couldn't think of a more straightforward and/or less cumbersome way of doing it, except for using the same shape of argument as expected by `open-network-stream'. Per usual, I would be open to reconsideration if a better way of doing it is suggested. For now, I went ahead and finally installed the attached patch on the master branch. Thanks for all the feedback/suggestions. :-) --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-Add-support-for-using-a-TLS-client-certificate-with-.patch >From 344f769491a84b6d47ee3722054b214167572219 Mon Sep 17 00:00:00 2001 From: Amin Bandali Date: Thu, 22 Apr 2021 20:22:38 -0400 Subject: [PATCH] Add support for using a TLS client certificate with 'erc-tls' (bug#47788) * 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): Clarify documentation string with respect to the full-name argument. (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 | 73 +++++++++++++++++++++++++++-- etc/NEWS | 36 +++++++++++++++ lisp/erc/erc-backend.el | 30 ++++++++---- lisp/erc/erc.el | 100 ++++++++++++++++++++++++++++++++-------- 4 files changed, 208 insertions(+), 31 deletions(-) diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi index d635cac5ab..45a753d43e 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -514,15 +514,82 @@ Connecting 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. +@code{erc-compute-port} and @code{erc-compute-nick} will be invoked +for the values of the other parameters. @example (erc :server "chat.freenode.net" :full-name "Harry S Truman") @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} and @code{erc-compute-nick} 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. Authenticating using a TLS client certificate is also +refered to as ``CertFP'' (Certificate Fingerprint) authentication by +various IRC networks. + +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 6fe4e98a50..34aeaf028b 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1746,6 +1746,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..43661a2fc4 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. @@ -2196,22 +2213,22 @@ erc "ERC is a powerful, modular, and extensible IRC client. This function is the main entry point for ERC. -It permits you to select connection parameters, and then starts ERC. +It allows selecting connection parameters, and then starts ERC. 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))) + (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." +then the server and full-name will be set to those values, +whereas `erc-compute-port' and `erc-compute-nick' 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,66 @@ '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 selecting 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' and `erc-compute-nick' 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. Authenticating using a TLS client certificate is +also refered to as \"CertFP\" (Certificate Fingerprint) +authentication by various IRC networks. + +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 --=-=-=--