Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugins: irc: support server certificate public key fingerprints #1507

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

aaronmdjones
Copy link

@aaronmdjones aaronmdjones commented May 13, 2020

This allows users to pin known public key fingerprints instead of (or in addition to) certificate fingerprints, which means that the fingerprints will not change when the server's certificate is renewed (unless that is accompanied by a change of server private key). With the advent of short-lifetime certificates from the likes of Let's Encrypt, this is a very useful feature.

This is not any more insecure than a certificate fingerprint; server certificates only exist to securely bind a server name to a cryptographic identity (its public key), so that you have some degree of confidence that you are talking to the correct server, and not an impostor (MITM). If you're establishing its cryptographic identity up-front (by pinning a known-good fingerprint of it with a cryptographically-secure digest algorithm), then the certificate is, by definition, not needed. In fact, this is slightly more secure than a certificate fingerprint, because certificates are a lot more malleable; the ability to pad and inject random data to aid in e.g. a second preimage attack is much more easily achieved with an X.509 certificate than it is with a public key (because you can generate a valid private key, compute its corresponding public key, and then mess with other fields in the certificate to affect the digest instead). Also, any attack against Hash(publickey) implies an attack against Hash(certificate), which would break the CA PKI model anyway.

Specifically, this fingerprints the entire SubjectPublicKeyInfo structure from the certificate, which includes not just the key itself, but also other meta-information about it (like its algorithm identifier and any associated parameters). This is the same public key fingerprinting approach taken by e.g. the HSTS and DANE TLSA standards, and also some IRC servers (such as Charybdis IRCd).

While working on this commit, I noticed a few oddities and/or deficiencies in the existing fingerprinting code, which this commit also corrects and/or improves:

  • Fingerprints were scanned 1 byte (2 hexadecimal characters) at a time and then compared 1 byte at a time, instead of using efficient C standard library string comparison functions.

  • Several for() loops used an unrelated variable to iterate over arrays of fingerprint variables (for setting their initial values or freeing them after).

  • When displaying that the server's fingerprint has matched the client's configuration, it did not specify which fingerprint was received from the server for matching. This is relevant in the case of a comma-separated list of fingerprints.

  • The code checked if a variable was non-NULL before calling free(3) on it, but free(3) is well-defined to do nothing if passed NULL.

cf. #879

@flashcode flashcode added the feature New feature request label May 13, 2020
@aaronmdjones
Copy link
Author

(If anyone needs to test this, you can obtain a SubjectPublicKeyInfo fingerprint from an X.509 certificate like so):

$ openssl x509 -pubkey -noout < foo.crt | openssl pkey -pubin -outform DER | sha256sum

@usrbinsam
Copy link

👍

Copy link
Member

@sim642 sim642 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a nice addition but I can't agree with the way the existing implementation of certificate fingerprints has taken a step back.

src/plugins/irc/irc-server.c Show resolved Hide resolved
Comment on lines 4315 to 4409
/* attempt to extract and fingerprint the certificate's public key */
if (gnutls_pubkey_init (&pk) != GNUTLS_E_SUCCESS)
{
weechat_printf (
server->buffer,
_("%sgnutls: failed to initialize public key object"),
weechat_prefix ("error"));
}
else
{
if (gnutls_pubkey_import_x509 (pk, certificate, 0)
!= GNUTLS_E_SUCCESS)
{
weechat_printf (
server->buffer,
_("%sgnutls: failed to import certificate public key"),
weechat_prefix ("error"));
}
else if (gnutls_pubkey_export (pk, GNUTLS_X509_FMT_DER, NULL,
&pkbuf_len) != GNUTLS_E_SHORT_MEMORY_BUFFER)
{
weechat_printf (
server->buffer,
_("%sgnutls: failed to export certificate public key"),
weechat_prefix ("error"));
}
else if (!pkbuf_len)
{
weechat_printf (
server->buffer,
_("%sgnutls: failed to export certificate public key"),
weechat_prefix ("error"));
}
else if (!(pkbuf = malloc (pkbuf_len)))
{
weechat_printf (
server->buffer,
_("%s%s: not enough memory (%s)"),
weechat_prefix ("error"), IRC_PLUGIN_NAME,
"fingerprint");
}
else if (gnutls_pubkey_export (pk, GNUTLS_X509_FMT_DER, pkbuf,
&pkbuf_len) != GNUTLS_E_SUCCESS)
{
weechat_printf (
server->buffer,
_("%sgnutls: failed to export certificate public key"),
weechat_prefix ("error"));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole public key extraction might deserve to be a separate function, if one doesn't already exist in GnuTLS.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does exist in GnuTLS, in that gnutls_pubkey_export is all you get. This scaffolding around it is necessary to prepare for that export.

There is no gnutls_x509_crt_get_* function that will hand you the SubjectPublicKeyInfo structure; the closest is gnutls_x509_crt_get_pk_rsa_raw which hands you the modulus and exponent separately (and would hardcode us to RSA).

I can move it to a separate function on request, but they would still be heavily tied together...

src/plugins/irc/irc-server.c Outdated Show resolved Hide resolved
src/plugins/irc/irc-server.c Outdated Show resolved Hide resolved
src/plugins/irc/irc-server.c Outdated Show resolved Hide resolved
aaronmdjones added a commit to AlphaChat/weechat that referenced this pull request May 14, 2020
@codecov-io
Copy link

Codecov Report

Merging #1507 into master will decrease coverage by 0.00%.
The diff coverage is 0.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1507      +/-   ##
==========================================
- Coverage   35.26%   35.25%   -0.01%     
==========================================
  Files         203      203              
  Lines       84164    84189      +25     
==========================================
+ Hits        29678    29681       +3     
- Misses      54486    54508      +22     
Impacted Files Coverage Δ
src/plugins/irc/irc-config.c 61.39% <ø> (ø)
src/plugins/irc/irc-server.c 36.44% <0.00%> (-0.24%) ⬇️
src/gui/gui-buffer.c 46.15% <0.00%> (-0.04%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4f4f32b...cb355e4. Read the comment docs.

aaronmdjones added a commit to AlphaChat/weechat that referenced this pull request May 14, 2020
This allows users to pin known public key fingerprints instead of
certificate fingerprints, which means that the fingerprints will
not change when the server's certificate is renewed (unless that
is accompanied by a change of server private key). With the
advent of short-lifetime certificates from the likes of Let's
Encrypt, this is a very useful feature.

This is not any more insecure than a certificate fingerprint;
server certificates only exist to securely bind a server name to
a cryptographic identity (its public key), so that you have some
degree of confidence that you are talking to the correct server,
and not an impostor (MITM). If you're establishing its crypto-
graphic identity up-front (by pinning a known-good fingerprint of
it with a cryptographically-secure digest algorithm), then the
certificate is, by definition, not needed. In fact, this is
slightly more secure than a certificate fingerprint, because
certificates are a lot more malleable; the ability to pad and
inject random data to aid in e.g. a second preimage attack is
much more easily achieved with an X.509 certificate than it is
with a public key (because you can generate a valid private key,
compute its corresponding public key, and then mess with other
fields in the certificate to affect the digest instead). Also,
any attack against Hash(publickey) implies an attack against
Hash(certificate), which would break the CA PKI model anyway.

Specifically, this fingerprints the entire SubjectPublicKeyInfo
structure from the certificate, which includes not just the key
itself, but also other meta-information about it (like its
algorithm identifier and any associated parameters). This is the
same public key fingerprinting approach taken by e.g. the HSTS
and DANE TLSA standards, and also some IRC servers (such as
Charybdis IRCd).

While working on this commit, I noticed a few oddities and/or
deficiencies in the existing fingerprinting code, which this
commit also corrects and/or improves:

- Several for() loops used an unrelated variable to iterate over
  arrays of fingerprint variables (for setting their initial
  values or freeing them after).

- When displaying that the server's fingerprint has matched the
  client's configuration, it did not specify which fingerprint
  was received from the server for matching. This is relevant in
  the case of a comma-separated list of fingerprints.

- The code checked if a variable was non-NULL before calling
  free(3) on it, but free(3) is well-defined to do nothing if
  passed NULL.

cf. weechat#879

Signed-off-by: Aaron Jones <aaron@alphachat.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants