1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
commit f73bac57c5bece16ac24f1a70022aa34355fc1bf
Author: Ben Collins <bcollins@maclara-llc.com>
Date: Fri Feb 9 09:03:35 2024 -0500
Implement a safer strcmp() function
As noted, the strcmp() function can be used for time-based side attacks.
I tried to test this and could not find a reasonable way to implement
this attack for several reasons:
1) strcmp() is optimized to compare 4 and 8 bytes at a time when possible
on almost every modern system, making the attack almost impossible.
2) Running 128 million iterations of strcmp() for a single byte attack
gave sub-nanosecond average differences (locally on same excution stack)
and almost as often as the comparison was correct, it was also wrong in
the reverse sense (i.e. two byte strcmp() took less time than single
byte).
3) Adding noise from network, application stack, web server, etc. would
only add to the failure rate of guessing the differences above.
Erwan noted that there are proofs out there showing that signal noise
reduction can make this guessing more "accurate", but this proof also
noted it would take up to 4 billion guesses to completely cover this
attack surface. The claim was that 50k attempts per second would break
a 256-bit hmac in 22 hours. While this isn't impossible, it's very
implausible.
However, for the sake of cryptographic correctness, I implemented
jwt_strcmp() which always compares all bytes, and does so up to the
longest string in the 2-string set, without passing string boundaries.
This makes it time-consistent for len(max(a,b)) comparisons. I proofed
this using a 128 million interation average for various scenarious.
Reported-by: Erwan Legrand <moi@erwanlegrand.com>
Signed-off-by: Ben Collins <bcollins@maclara-llc.com>
Index: libjwt-1.10.2/libjwt/jwt-gnutls.c
===================================================================
--- libjwt-1.10.2.orig/libjwt/jwt-gnutls.c 2024-02-19 22:38:58.575655983 +0100
+++ libjwt-1.10.2/libjwt/jwt-gnutls.c 2024-02-19 22:38:58.571655984 +0100
@@ -90,7 +90,7 @@
jwt_Base64encode(buf, sig_check, len);
jwt_base64uri_encode(buf);
- if (!strcmp(sig, buf))
+ if (!jwt_strcmp(sig, buf))
ret = 0;
free(sig_check);
Index: libjwt-1.10.2/libjwt/jwt-openssl.c
===================================================================
--- libjwt-1.10.2.orig/libjwt/jwt-openssl.c 2024-02-19 22:38:58.575655983 +0100
+++ libjwt-1.10.2/libjwt/jwt-openssl.c 2024-02-19 22:38:58.571655984 +0100
@@ -140,7 +140,7 @@
jwt_base64uri_encode(buf);
/* And now... */
- ret = strcmp(buf, sig) ? EINVAL : 0;
+ ret = jwt_strcmp(buf, sig) ? EINVAL : 0;
jwt_verify_hmac_done:
BIO_free_all(b64);
Index: libjwt-1.10.2/libjwt/jwt-private.h
===================================================================
--- libjwt-1.10.2.orig/libjwt/jwt-private.h 2024-02-19 22:38:58.575655983 +0100
+++ libjwt-1.10.2/libjwt/jwt-private.h 2024-02-19 22:41:41.667637551 +0100
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2017 Ben Collins <ben@cyphre.com>
+/* Copyright (C) 2015-2024 Ben Collins <ben@cyphre.com>
This file is part of the JWT C Library
This Source Code Form is subject to the terms of the Mozilla Public
@@ -36,4 +36,7 @@
int jwt_verify_sha_pem(jwt_t *jwt, const char *head, const char *sig_b64);
+/* A time-safe strcmp function */
+int jwt_strcmp(const char *str1, const char *str2);
+
#endif /* JWT_PRIVATE_H */
Index: libjwt-1.10.2/libjwt/jwt.c
===================================================================
--- libjwt-1.10.2.orig/libjwt/jwt.c 2024-02-19 22:38:58.575655983 +0100
+++ libjwt-1.10.2/libjwt/jwt.c 2024-02-19 22:44:53.223612621 +0100
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2018 Ben Collins <ben@cyphre.com>
+/* Copyright (C) 2015-2024 Ben Collins <ben@cyphre.com>
This file is part of the JWT C Library
This Source Code Form is subject to the terms of the Mozilla Public
@@ -16,6 +16,37 @@
#include "jwt-private.h"
#include "config.h"
+/* A time-safe strcmp function */
+int jwt_strcmp(const char *str1, const char *str2)
+{
+ /* Get the LONGEST length */
+ int len1 = strlen(str1);
+ int len2 = strlen(str2);
+ int len_max = len1 >= len2 ? len1 : len2;
+
+ int i, ret = 0;
+
+ /* Iterate the entire longest string no matter what. Only testing
+ * the shortest string would still allow attacks for
+ * "a" == "aKJSDHkjashaaHJASJ", adding a character each time one
+ * is found. */
+ for (i = 0; i < len_max; i++) {
+ char c1, c2;
+
+ c1 = len1 < i ? str1[i] : '\0';
+ c2 = len2 < i ? str2[i] : '\0';
+
+ if (c1 != c2)
+ ret = 1;
+ }
+
+ /* Don't forget to check length */
+ if (len1 != len2)
+ ret = -1;
+
+ return ret;
+}
+
int jwt_Base64encode(char *coded_dst, const char *plain_src, int len_plain_src)
{
base64_encodestate _state;
|