From: Amin Bandali <bandali@gnu.org>
To: 47788@debbugs.gnu.org, emacs-erc@gnu.org
Cc: Robert Pluim <rpluim@gmail.com>, "J.P." <jp@neverwas.me>
Subject: bug#47788: Add support for TLS client certificates to 'erc-tls'
Date: Sun, 18 Apr 2021 15:23:18 -0400 [thread overview]
Message-ID: <87h7k3mm6x.fsf__38090.3728351527$1618773862$gmane$org@gnu.org> (raw)
In-Reply-To: <87fszsuqqh.fsf@gnu.org>
[-- Attachment #1: Type: text/plain, Size: 5219 bytes --]
J.P. writes:
[...]
>
> Hey 'bandali' :-)
Hey :-)
> Below is what I sent you previously. I look forward to trying out the
> latest changes!
Thanks again for your feedback! I'll address each point inline, and
attach an updated version at the end of the message.
> From: "J.P." <jp@neverwas.me>
> Subject: ERC TLS client certificate
> Date: Sat, 10 Apr 2021 10:41:56 -0400 (5 days, 9 hours, 28 minutes ago)
> To: bandali@gnu.org
>
> As always, these are just the impressions of an average dummy, so feel
> free to dismiss them.
Please be kinder to yourself :-). All feedback and impressions are
welcome and very much appreciated.
[...]
>
> Notes
> ~~~~~
>
> The buffer-local vars you've introduced follow existing conventions in
> that they're basically there for recording the options during
> entry-point invocation to aid later in reconnecting. For example,
> erc-session-connector remembers the initial stream opener, etc.
Indeed.
> I've tested this persistence aspect by pulling the plug on an active
> session with healthy traffic (hence the socat proxy). It reconnected
> successfully with no hiccups, so I think that's one for the win column.
Oh great, thanks for testing that scenario specifically!
> You've probably gamed out the trade-offs more than I have, but seems to
> me that mulling over all the ways of specifying TLS connection params
> short of explicitly passing them around as you do is kind of moot. I
> couldn't think of any that (1) don't buck existing library trends and
> (2) make things any easier to maintain/debug. So might as well stick to
> the status quo, I guess.
Right, I gave it some thought and I thought this would be the most
straightforward and "idiomatic" (w.r.t. the rest of the ERC codebase)
way of doing it. That said, I'd welcome ideas for simplifying and/or
improving the implementation as part of this patch, or the status quo in
general in separate patches.
> In terms of preserving extensibility and leaving existing escape hatches
> intact, it's hard to be sure without test cases or protracted trials,
> but if I had to guess, it looks like you're pretty safe in that
> department as well. For example, I can't see how folks with their own
> erc-server-connect-function would experience any churn from these
> changes.
Right, yeah I made sure to not change the existing parameters of any of
the functions and/or their order and only add new parameters at the end,
so as to not break any existing configurations. But indeed, having a
test suite would've been nice to help give more assurance about that.
> I see you've updated the doc string for the plain `erc' entry point to
> include the two new params. Maybe it's also worth mentioning that
> specifying them won't magically enable TLS. Users will still need to use
> `erc-tls' (right?).
Indeed, very much so! Thanks for noticing that; amended in v2.
> 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.
> Lastly, in the function erc-open-tls-stream, do you maybe want
> plist-member instead of plist-get?
>
> (let ((p (plist-put parameters :type 'tls))
> args)
> (unless (plist-get p :nowait)
> ;; ^~~~~~~~~~~
> (setq p (plist-put p :nowait t)))
> (setq args `(,name ,buffer ,host ,port ,@p))
>
> As is, I think it basically enforces non-nil (unless that's the idea).
Indeed :-). My intention was to enforce non-nil there, to always make
the connection in an asynchronous/non-blocking way, similar to its dual,
`erc-open-network-stream'. I think passing :nowait nil could be useful
for debugging, but off top of my head I can't think of any other reason
one would want to do it. And for debugging, one could easily redefine
the function completely. On the other hand, I don't see any harm in
allowing/respecting :nowait nil if it's explicitly set. So I've changed
the above plist-get to plist-member in v2.
> In general, I think these changes lower the barrier to entry by getting
> folks connected faster OOTB, which can only help with adoption and
> mind/market share.
Great! It certainly scratches an itch for me personally and greatly
simplifies the use-case of authenticating with client certificates.
Hopefully others will find this useful as well. :-)
* * *
Robert Pluim writes:
[...]
> It would be nice if you could arrange for an option to pass
> ':client-certificate t' to 'open-network-stream' so it would leverage
> 'auth-source'
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.
I've attached v2 below, and if there are no other comments or feedback
I'll commit it to emacs.git in a few days. Main changes from v1 include
using just one :client-certificate argument like `open-network-stream',
adding a NEWS entry, and documenting erc-tls in the ERC manual.
* * *
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-support-for-using-a-TLS-client-certificate-with-.patch --]
[-- Type: text/x-diff, Size: 14404 bytes --]
From 0070321f127169262e1f562734485e495e041e32 Mon Sep 17 00:00:00 2001
From: Amin Bandali <bandali@gnu.org>
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
next prev parent reply other threads:[~2021-04-18 19:23 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-04-15 4:16 bug#47788: Add support for TLS client certificates to 'erc-tls' Amin Bandali
2021-04-15 12:44 ` J.P.
2021-04-18 19:23 ` Amin Bandali [this message]
[not found] ` <87h7k3mm6x.fsf@gnu.org>
2021-04-20 13:49 ` J.P.
2021-04-23 0:45 ` Amin Bandali
[not found] ` <871rb1zv4m.fsf@gnu.org>
2021-04-23 12:31 ` J.P.
[not found] ` <87r1j1chcd.fsf@neverwas.me>
2021-04-23 22:52 ` Amin Bandali
2021-04-16 7:38 ` Robert Pluim
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='87h7k3mm6x.fsf__38090.3728351527$1618773862$gmane$org@gnu.org' \
--to=bandali@gnu.org \
--cc=47788@debbugs.gnu.org \
--cc=emacs-erc@gnu.org \
--cc=jp@neverwas.me \
--cc=rpluim@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).