Coverage Report

Created: 2026-04-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/ws_accept_key.h
Line
Count
Source
1
/*
2
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3
 *
4
 * SPDX-License-Identifier: curl
5
 */
6
7
/// @file
8
/// @brief Minimal standalone SHA-1 + base64 for the WebSocket Sec-WebSocket-Accept
9
///        computation (RFC 6455 §4.2.2).  This removes the hard OpenSSL dependency
10
///        from the proto fuzzer — the only crypto it needs is a single SHA-1 hash
11
///        of a handshake nonce which has no security implications inside a fuzzer.
12
13
#ifndef PROTO_FUZZER_WS_ACCEPT_KEY_H_
14
#define PROTO_FUZZER_WS_ACCEPT_KEY_H_
15
16
#include <array>
17
#include <cstddef>
18
#include <cstdint>
19
#include <cstring>
20
#include <string>
21
22
namespace proto_fuzzer {
23
namespace detail {
24
25
/// Minimal SHA-1 (FIPS 180-4) — not for security use; only for the WS accept key.
26
/// @param data Pointer to the input bytes to hash.
27
/// @param len  Number of input bytes.
28
/// @return 20-byte SHA-1 digest.
29
718
inline std::array<uint8_t, 20> Sha1(const uint8_t *data, std::size_t len) {
30
  // Initial hash values.
31
718
  uint32_t h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE, h3 = 0x10325476, h4 = 0xC3D2E1F0;
32
33
  // Pre-processing: build the padded message.
34
718
  uint64_t bit_len = static_cast<uint64_t>(len) * 8;
35
  // Number of bytes after original message: 1 (0x80) + padding + 8 (length).
36
718
  std::size_t padded_len = ((len + 8) / 64 + 1) * 64;
37
  // Use a small stack buffer for typical WebSocket key sizes (< 128 bytes).
38
  // Fall back to heap for anything larger.
39
718
  uint8_t stack_buf[128];
40
718
  uint8_t *msg;
41
718
  bool heap = padded_len > sizeof(stack_buf);
42
718
  if (heap) {
43
0
    msg = new uint8_t[padded_len]();
44
718
  } else {
45
718
    msg = stack_buf;
46
718
    std::memset(msg, 0, padded_len);
47
718
  }
48
718
  std::memcpy(msg, data, len);
49
718
  msg[len] = 0x80;
50
  // Append original length in bits as big-endian 64-bit.
51
6.46k
  for (int i = 0; i < 8; ++i) {
52
5.74k
    msg[padded_len - 1 - i] = static_cast<uint8_t>(bit_len >> (i * 8));
53
5.74k
  }
54
55
321k
  auto left_rotate = [](uint32_t v, unsigned n) -> uint32_t { return (v << n) | (v >> (32 - n)); };
56
57
2.15k
  for (std::size_t offset = 0; offset < padded_len; offset += 64) {
58
1.43k
    uint32_t w[80];
59
24.4k
    for (int i = 0; i < 16; ++i) {
60
22.9k
      w[i] = static_cast<uint32_t>(msg[offset + 4 * i]) << 24 | static_cast<uint32_t>(msg[offset + 4 * i + 1]) << 16 |
61
22.9k
             static_cast<uint32_t>(msg[offset + 4 * i + 2]) << 8 | static_cast<uint32_t>(msg[offset + 4 * i + 3]);
62
22.9k
    }
63
93.3k
    for (int i = 16; i < 80; ++i) {
64
91.9k
      w[i] = left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
65
91.9k
    }
66
1.43k
    uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;
67
116k
    for (int i = 0; i < 80; ++i) {
68
114k
      uint32_t f, k;
69
114k
      if (i < 20) {
70
28.7k
        f = (b & c) | ((~b) & d);
71
28.7k
        k = 0x5A827999;
72
86.1k
      } else if (i < 40) {
73
28.7k
        f = b ^ c ^ d;
74
28.7k
        k = 0x6ED9EBA1;
75
57.4k
      } else if (i < 60) {
76
28.7k
        f = (b & c) | (b & d) | (c & d);
77
28.7k
        k = 0x8F1BBCDC;
78
28.7k
      } else {
79
28.7k
        f = b ^ c ^ d;
80
28.7k
        k = 0xCA62C1D6;
81
28.7k
      }
82
114k
      uint32_t temp = left_rotate(a, 5) + f + e + k + w[i];
83
114k
      e = d;
84
114k
      d = c;
85
114k
      c = left_rotate(b, 30);
86
114k
      b = a;
87
114k
      a = temp;
88
114k
    }
89
1.43k
    h0 += a;
90
1.43k
    h1 += b;
91
1.43k
    h2 += c;
92
1.43k
    h3 += d;
93
1.43k
    h4 += e;
94
1.43k
  }
95
96
718
  if (heap) {
97
0
    delete[] msg;
98
0
  }
99
100
718
  std::array<uint8_t, 20> digest;
101
3.59k
  auto store = [&](int idx, uint32_t val) {
102
3.59k
    digest[idx * 4 + 0] = static_cast<uint8_t>(val >> 24);
103
3.59k
    digest[idx * 4 + 1] = static_cast<uint8_t>(val >> 16);
104
3.59k
    digest[idx * 4 + 2] = static_cast<uint8_t>(val >> 8);
105
3.59k
    digest[idx * 4 + 3] = static_cast<uint8_t>(val);
106
3.59k
  };
107
718
  store(0, h0);
108
718
  store(1, h1);
109
718
  store(2, h2);
110
718
  store(3, h3);
111
718
  store(4, h4);
112
718
  return digest;
113
718
}
114
115
/// Minimal base64 encoder.
116
/// @param data Pointer to the input bytes to encode.
117
/// @param len  Number of input bytes.
118
/// @return Base64-encoded string.
119
718
inline std::string Base64Encode(const uint8_t *data, std::size_t len) {
120
718
  static const char kTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
121
718
  std::string out;
122
718
  out.reserve(4 * ((len + 2) / 3));
123
5.74k
  for (std::size_t i = 0; i < len; i += 3) {
124
5.02k
    uint32_t n = static_cast<uint32_t>(data[i]) << 16;
125
5.02k
    if (i + 1 < len) n |= static_cast<uint32_t>(data[i + 1]) << 8;
126
5.02k
    if (i + 2 < len) n |= static_cast<uint32_t>(data[i + 2]);
127
5.02k
    out.push_back(kTable[(n >> 18) & 0x3F]);
128
5.02k
    out.push_back(kTable[(n >> 12) & 0x3F]);
129
5.02k
    out.push_back((i + 1 < len) ? kTable[(n >> 6) & 0x3F] : '=');
130
5.02k
    out.push_back((i + 2 < len) ? kTable[n & 0x3F] : '=');
131
5.02k
  }
132
718
  return out;
133
718
}
134
135
}  // namespace detail
136
137
/// Compute the RFC 6455 Sec-WebSocket-Accept value for a given client key.
138
/// @param key The client-supplied Sec-WebSocket-Key header value.
139
/// @return The base64-encoded Sec-WebSocket-Accept string.
140
718
inline std::string ComputeWebSocketAcceptKey(const std::string &key) {
141
718
  static const char kGuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
142
718
  std::string combined = key + kGuid;
143
718
  auto digest = detail::Sha1(reinterpret_cast<const uint8_t *>(combined.data()), combined.size());
144
718
  return detail::Base64Encode(digest.data(), digest.size());
145
718
}
146
147
}  // namespace proto_fuzzer
148
149
#endif  // PROTO_FUZZER_WS_ACCEPT_KEY_H_