s2n_fingerprint.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /*
  2. * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License").
  5. * You may not use this file except in compliance with the License.
  6. * A copy of the License is located at
  7. *
  8. * http://aws.amazon.com/apache2.0
  9. *
  10. * or in the "license" file accompanying this file. This file is distributed
  11. * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
  12. * express or implied. See the License for the specific language governing
  13. * permissions and limitations under the License.
  14. */
  15. #include "api/unstable/fingerprint.h"
  16. #include "crypto/s2n_fips.h"
  17. #include "crypto/s2n_hash.h"
  18. #include "stuffer/s2n_stuffer.h"
  19. #include "tls/extensions/s2n_extension_list.h"
  20. #include "tls/s2n_client_hello.h"
  21. #include "tls/s2n_crypto_constants.h"
  22. #include "utils/s2n_blob.h"
  23. #include "utils/s2n_result.h"
  24. #include "utils/s2n_safety.h"
  25. #define S2N_JA3_FIELD_DIV ','
  26. #define S2N_JA3_LIST_DIV '-'
  27. /* UINT16_MAX == 65535 */
  28. #define S2N_UINT16_STR_MAX_SIZE 5
  29. /* See https://datatracker.ietf.org/doc/html/rfc8701
  30. * for an explanation of GREASE and lists of the GREASE values.
  31. */
  32. static S2N_RESULT s2n_assert_grease_value(uint16_t val)
  33. {
  34. uint8_t byte1 = val >> 8;
  35. uint8_t byte2 = val & 0x00FF;
  36. /* Both bytes of the GREASE values are identical */
  37. RESULT_ENSURE_EQ(byte1, byte2);
  38. /* The GREASE value bytes all follow the format 0x[0-F]A.
  39. * So 0x0A, 0x1A, 0x2A etc, up to 0xFA. */
  40. RESULT_ENSURE_EQ((byte1 | 0xF0), 0xFA);
  41. return S2N_RESULT_OK;
  42. }
  43. static bool s2n_is_grease_value(uint16_t val)
  44. {
  45. return s2n_result_is_ok(s2n_assert_grease_value(val));
  46. }
  47. static S2N_RESULT s2n_fingerprint_hash_flush(struct s2n_hash_state *hash, struct s2n_stuffer *in)
  48. {
  49. if (hash == NULL) {
  50. /* If the buffer is full and needs to be flushed, but no hash was provided,
  51. * then we have insufficient memory to complete the fingerprint.
  52. *
  53. * The application will need to provide a larger buffer.
  54. */
  55. RESULT_BAIL(S2N_ERR_INSUFFICIENT_MEM_SIZE);
  56. }
  57. uint32_t hash_data_len = s2n_stuffer_data_available(in);
  58. uint8_t *hash_data = s2n_stuffer_raw_read(in, hash_data_len);
  59. RESULT_ENSURE_REF(hash_data);
  60. RESULT_GUARD_POSIX(s2n_hash_update(hash, hash_data, hash_data_len));
  61. RESULT_GUARD_POSIX(s2n_stuffer_wipe(in));
  62. return S2N_RESULT_OK;
  63. }
  64. static S2N_RESULT s2n_fingerprint_write_char(struct s2n_stuffer *stuffer,
  65. char c, struct s2n_hash_state *hash)
  66. {
  67. if (s2n_stuffer_space_remaining(stuffer) < 1) {
  68. RESULT_GUARD(s2n_fingerprint_hash_flush(hash, stuffer));
  69. }
  70. RESULT_GUARD_POSIX(s2n_stuffer_write_char(stuffer, c));
  71. return S2N_RESULT_OK;
  72. }
  73. static S2N_RESULT s2n_fingerprint_write_entry(struct s2n_stuffer *stuffer,
  74. bool *is_list, uint16_t value, struct s2n_hash_state *hash)
  75. {
  76. /* If we have already written at least one value for this field,
  77. * then we are writing a list and need to prepend a list divider before
  78. * writing the next value.
  79. */
  80. RESULT_ENSURE_REF(is_list);
  81. if (*is_list) {
  82. RESULT_GUARD(s2n_fingerprint_write_char(stuffer, S2N_JA3_LIST_DIV, hash));
  83. }
  84. *is_list = true;
  85. /* snprintf always appends a '\0' to the output,
  86. * but that extra '\0' is not included in the return value */
  87. uint8_t entry[S2N_UINT16_STR_MAX_SIZE + 1] = { 0 };
  88. int written = snprintf((char *) entry, sizeof(entry), "%u", value);
  89. RESULT_ENSURE_GT(written, 0);
  90. RESULT_ENSURE_LTE(written, S2N_UINT16_STR_MAX_SIZE);
  91. if (s2n_stuffer_space_remaining(stuffer) < (uint64_t) written) {
  92. RESULT_GUARD(s2n_fingerprint_hash_flush(hash, stuffer));
  93. }
  94. RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(stuffer, entry, written));
  95. return S2N_RESULT_OK;
  96. }
  97. static S2N_RESULT s2n_fingerprint_write_version(struct s2n_client_hello *ch,
  98. struct s2n_stuffer *output, struct s2n_hash_state *hash)
  99. {
  100. RESULT_ENSURE_REF(ch);
  101. bool is_list = false;
  102. uint16_t version = 0;
  103. struct s2n_stuffer message = { 0 };
  104. RESULT_GUARD_POSIX(s2n_stuffer_init_written(&message, &ch->raw_message));
  105. RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&message, &version));
  106. RESULT_GUARD(s2n_fingerprint_write_entry(output, &is_list, version, hash));
  107. return S2N_RESULT_OK;
  108. }
  109. static S2N_RESULT s2n_fingerprint_write_ciphers(struct s2n_client_hello *ch,
  110. struct s2n_stuffer *output, struct s2n_hash_state *hash)
  111. {
  112. RESULT_ENSURE_REF(ch);
  113. bool cipher_found = false;
  114. struct s2n_stuffer ciphers = { 0 };
  115. RESULT_GUARD_POSIX(s2n_stuffer_init_written(&ciphers, &ch->cipher_suites));
  116. while (s2n_stuffer_data_available(&ciphers)) {
  117. uint16_t cipher = 0;
  118. RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&ciphers, &cipher));
  119. if (s2n_is_grease_value(cipher)) {
  120. continue;
  121. }
  122. RESULT_GUARD(s2n_fingerprint_write_entry(output, &cipher_found, cipher, hash));
  123. }
  124. return S2N_RESULT_OK;
  125. }
  126. static S2N_RESULT s2n_fingerprint_write_extensions(struct s2n_client_hello *ch,
  127. struct s2n_stuffer *output, struct s2n_hash_state *hash)
  128. {
  129. RESULT_ENSURE_REF(ch);
  130. /* We have to use the raw extensions instead of the parsed extensions
  131. * because s2n-tls both intentionally ignores any unknown extensions
  132. * and reorders the extensions when parsing the list.
  133. */
  134. struct s2n_stuffer extensions = { 0 };
  135. RESULT_GUARD_POSIX(s2n_stuffer_init_written(&extensions, &ch->extensions.raw));
  136. bool extension_found = false;
  137. while (s2n_stuffer_data_available(&extensions)) {
  138. uint16_t extension = 0, extension_size = 0;
  139. RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&extensions, &extension));
  140. RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&extensions, &extension_size));
  141. RESULT_GUARD_POSIX(s2n_stuffer_skip_read(&extensions, extension_size));
  142. if (s2n_is_grease_value(extension)) {
  143. continue;
  144. }
  145. RESULT_GUARD(s2n_fingerprint_write_entry(output, &extension_found, extension, hash));
  146. }
  147. return S2N_RESULT_OK;
  148. }
  149. static S2N_RESULT s2n_fingerprint_write_elliptic_curves(struct s2n_client_hello *ch,
  150. struct s2n_stuffer *output, struct s2n_hash_state *hash)
  151. {
  152. RESULT_ENSURE_REF(ch);
  153. s2n_parsed_extension *elliptic_curves_extension = NULL;
  154. int result = s2n_client_hello_get_parsed_extension(S2N_EXTENSION_SUPPORTED_GROUPS,
  155. &ch->extensions, &elliptic_curves_extension);
  156. if (result != S2N_SUCCESS) {
  157. return S2N_RESULT_OK;
  158. }
  159. struct s2n_stuffer elliptic_curves = { 0 };
  160. RESULT_GUARD_POSIX(s2n_stuffer_init_written(&elliptic_curves,
  161. &elliptic_curves_extension->extension));
  162. uint16_t count = 0;
  163. RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&elliptic_curves, &count));
  164. bool curve_found = false;
  165. while (s2n_stuffer_data_available(&elliptic_curves)) {
  166. uint16_t curve = 0;
  167. RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&elliptic_curves, &curve));
  168. if (s2n_is_grease_value(curve)) {
  169. continue;
  170. }
  171. RESULT_GUARD(s2n_fingerprint_write_entry(output, &curve_found, curve, hash));
  172. }
  173. return S2N_RESULT_OK;
  174. }
  175. static S2N_RESULT s2n_fingerprint_write_point_formats(struct s2n_client_hello *ch,
  176. struct s2n_stuffer *output, struct s2n_hash_state *hash)
  177. {
  178. RESULT_ENSURE_REF(ch);
  179. s2n_parsed_extension *point_formats_extension = NULL;
  180. int result = s2n_client_hello_get_parsed_extension(S2N_EXTENSION_EC_POINT_FORMATS,
  181. &ch->extensions, &point_formats_extension);
  182. if (result != S2N_SUCCESS) {
  183. return S2N_RESULT_OK;
  184. }
  185. struct s2n_stuffer point_formats = { 0 };
  186. RESULT_GUARD_POSIX(s2n_stuffer_init_written(&point_formats,
  187. &point_formats_extension->extension));
  188. uint8_t count = 0;
  189. RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(&point_formats, &count));
  190. bool format_found = false;
  191. while (s2n_stuffer_data_available(&point_formats)) {
  192. uint8_t format = 0;
  193. RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(&point_formats, &format));
  194. RESULT_GUARD(s2n_fingerprint_write_entry(output, &format_found, format, hash));
  195. }
  196. return S2N_RESULT_OK;
  197. }
  198. /* JA3 involves concatenating a set of fields from the ClientHello:
  199. * SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat
  200. * For example:
  201. * "769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0"
  202. * See https://github.com/salesforce/ja3
  203. */
  204. static S2N_RESULT s2n_fingerprint_ja3(struct s2n_client_hello *ch,
  205. struct s2n_stuffer *output, uint32_t *output_size, struct s2n_hash_state *hash)
  206. {
  207. RESULT_ENSURE_REF(ch);
  208. RESULT_ENSURE(!ch->sslv2, S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED);
  209. RESULT_GUARD(s2n_fingerprint_write_version(ch, output, hash));
  210. RESULT_GUARD(s2n_fingerprint_write_char(output, S2N_JA3_FIELD_DIV, hash));
  211. RESULT_GUARD(s2n_fingerprint_write_ciphers(ch, output, hash));
  212. RESULT_GUARD(s2n_fingerprint_write_char(output, S2N_JA3_FIELD_DIV, hash));
  213. RESULT_GUARD(s2n_fingerprint_write_extensions(ch, output, hash));
  214. RESULT_GUARD(s2n_fingerprint_write_char(output, S2N_JA3_FIELD_DIV, hash));
  215. RESULT_GUARD(s2n_fingerprint_write_elliptic_curves(ch, output, hash));
  216. RESULT_GUARD(s2n_fingerprint_write_char(output, S2N_JA3_FIELD_DIV, hash));
  217. RESULT_GUARD(s2n_fingerprint_write_point_formats(ch, output, hash));
  218. return S2N_RESULT_OK;
  219. }
  220. int s2n_client_hello_get_fingerprint_hash(struct s2n_client_hello *ch, s2n_fingerprint_type type,
  221. uint32_t max_hash_size, uint8_t *hash, uint32_t *hash_size, uint32_t *str_size)
  222. {
  223. POSIX_ENSURE(type == S2N_FINGERPRINT_JA3, S2N_ERR_INVALID_ARGUMENT);
  224. POSIX_ENSURE(max_hash_size >= MD5_DIGEST_LENGTH, S2N_ERR_INSUFFICIENT_MEM_SIZE);
  225. POSIX_ENSURE_REF(hash);
  226. POSIX_ENSURE_REF(hash_size);
  227. POSIX_ENSURE_REF(str_size);
  228. *hash_size = 0;
  229. *str_size = 0;
  230. /* The maximum size of the JA3 string is variable and could theoretically
  231. * be extremely large. However, we don't need enough memory to hold the full
  232. * string when calculating a hash. We can calculate and add the JA3 string
  233. * to the hash in chunks, similarly to how the TLS transcript hash is
  234. * calculated by adding handshake messages to the hash as they become
  235. * available. After a chunk is added to the hash, the string buffer can be
  236. * wiped and reused for the next chunk.
  237. *
  238. * The size of this buffer was chosen fairly arbitrarily.
  239. */
  240. uint8_t string_mem[50] = { 0 };
  241. struct s2n_blob string_blob = { 0 };
  242. struct s2n_stuffer string_stuffer = { 0 };
  243. POSIX_GUARD(s2n_blob_init(&string_blob, string_mem, sizeof(string_mem)));
  244. POSIX_GUARD(s2n_stuffer_init(&string_stuffer, &string_blob));
  245. /* JA3 uses an MD5 hash.
  246. * The hash doesn't have to be cryptographically secure,
  247. * so the weakness of MD5 shouldn't be a problem.
  248. */
  249. DEFER_CLEANUP(struct s2n_hash_state md5_hash = { 0 }, s2n_hash_free);
  250. POSIX_GUARD(s2n_hash_new(&md5_hash));
  251. if (s2n_is_in_fips_mode()) {
  252. /* This hash is unrelated to TLS and does not affect FIPS */
  253. POSIX_GUARD(s2n_hash_allow_md5_for_fips(&md5_hash));
  254. }
  255. POSIX_GUARD(s2n_hash_init(&md5_hash, S2N_HASH_MD5));
  256. POSIX_GUARD_RESULT(s2n_fingerprint_ja3(ch, &string_stuffer, hash_size, &md5_hash));
  257. POSIX_GUARD_RESULT(s2n_fingerprint_hash_flush(&md5_hash, &string_stuffer));
  258. uint64_t in_hash = 0;
  259. POSIX_GUARD(s2n_hash_get_currently_in_hash_total(&md5_hash, &in_hash));
  260. POSIX_ENSURE_LTE(in_hash, UINT32_MAX);
  261. *str_size = in_hash;
  262. POSIX_GUARD(s2n_hash_digest(&md5_hash, hash, MD5_DIGEST_LENGTH));
  263. *hash_size = MD5_DIGEST_LENGTH;
  264. return S2N_SUCCESS;
  265. }
  266. int s2n_client_hello_get_fingerprint_string(struct s2n_client_hello *ch, s2n_fingerprint_type type,
  267. uint32_t max_size, uint8_t *output, uint32_t *output_size)
  268. {
  269. POSIX_ENSURE(type == S2N_FINGERPRINT_JA3, S2N_ERR_INVALID_ARGUMENT);
  270. POSIX_ENSURE(max_size > 0, S2N_ERR_INSUFFICIENT_MEM_SIZE);
  271. POSIX_ENSURE_REF(output);
  272. POSIX_ENSURE_REF(output_size);
  273. *output_size = 0;
  274. struct s2n_blob output_blob = { 0 };
  275. struct s2n_stuffer output_stuffer = { 0 };
  276. POSIX_GUARD(s2n_blob_init(&output_blob, output, max_size));
  277. POSIX_GUARD(s2n_stuffer_init(&output_stuffer, &output_blob));
  278. POSIX_GUARD_RESULT(s2n_fingerprint_ja3(ch, &output_stuffer, output_size, NULL));
  279. *output_size = s2n_stuffer_data_available(&output_stuffer);
  280. return S2N_SUCCESS;
  281. }