Coverage Report

Created: 2026-07-16 06:34

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
1.54k
inline std::array<uint8_t, 20> Sha1(const uint8_t *data, std::size_t len) {
30
  // Initial hash values.
31
1.54k
  uint32_t h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE, h3 = 0x10325476, h4 = 0xC3D2E1F0;
32
33
  // Pre-processing: build the padded message.
34
1.54k
  uint64_t bit_len = static_cast<uint64_t>(len) * 8;
35
  // Number of bytes after original message: 1 (0x80) + padding + 8 (length).
36
1.54k
  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
1.54k
  uint8_t stack_buf[128];
40
1.54k
  uint8_t *msg;
41
1.54k
  bool heap = padded_len > sizeof(stack_buf);
42
1.54k
  if (heap) {
43
55
    msg = new uint8_t[padded_len]();
44
1.48k
  } else {
45
1.48k
    msg = stack_buf;
46
1.48k
    std::memset(msg, 0, padded_len);
47
1.48k
  }
48
1.54k
  std::memcpy(msg, data, len);
49
1.54k
  msg[len] = 0x80;
50
  // Append original length in bits as big-endian 64-bit.
51
13.8k
  for (int i = 0; i < 8; ++i) {
52
12.3k
    msg[padded_len - 1 - i] = static_cast<uint8_t>(bit_len >> (i * 8));
53
12.3k
  }
54
55
1.35M
  auto left_rotate = [](uint32_t v, unsigned n) -> uint32_t { return (v << n) | (v >> (32 - n)); };
56
57
7.60k
  for (std::size_t offset = 0; offset < padded_len; offset += 64) {
58
6.06k
    uint32_t w[80];
59
103k
    for (int i = 0; i < 16; ++i) {
60
96.9k
      w[i] = static_cast<uint32_t>(msg[offset + 4 * i]) << 24 | static_cast<uint32_t>(msg[offset + 4 * i + 1]) << 16 |
61
96.9k
             static_cast<uint32_t>(msg[offset + 4 * i + 2]) << 8 | static_cast<uint32_t>(msg[offset + 4 * i + 3]);
62
96.9k
    }
63
393k
    for (int i = 16; i < 80; ++i) {
64
387k
      w[i] = left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
65
387k
    }
66
6.06k
    uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;
67
490k
    for (int i = 0; i < 80; ++i) {
68
484k
      uint32_t f, k;
69
484k
      if (i < 20) {
70
121k
        f = (b & c) | ((~b) & d);
71
121k
        k = 0x5A827999;
72
363k
      } else if (i < 40) {
73
121k
        f = b ^ c ^ d;
74
121k
        k = 0x6ED9EBA1;
75
242k
      } else if (i < 60) {
76
121k
        f = (b & c) | (b & d) | (c & d);
77
121k
        k = 0x8F1BBCDC;
78
121k
      } else {
79
121k
        f = b ^ c ^ d;
80
121k
        k = 0xCA62C1D6;
81
121k
      }
82
484k
      uint32_t temp = left_rotate(a, 5) + f + e + k + w[i];
83
484k
      e = d;
84
484k
      d = c;
85
484k
      c = left_rotate(b, 30);
86
484k
      b = a;
87
484k
      a = temp;
88
484k
    }
89
6.06k
    h0 += a;
90
6.06k
    h1 += b;
91
6.06k
    h2 += c;
92
6.06k
    h3 += d;
93
6.06k
    h4 += e;
94
6.06k
  }
95
96
1.54k
  if (heap) {
97
55
    delete[] msg;
98
55
  }
99
100
1.54k
  std::array<uint8_t, 20> digest;
101
7.71k
  auto store = [&](int idx, uint32_t val) {
102
7.71k
    digest[idx * 4 + 0] = static_cast<uint8_t>(val >> 24);
103
7.71k
    digest[idx * 4 + 1] = static_cast<uint8_t>(val >> 16);
104
7.71k
    digest[idx * 4 + 2] = static_cast<uint8_t>(val >> 8);
105
7.71k
    digest[idx * 4 + 3] = static_cast<uint8_t>(val);
106
7.71k
  };
107
1.54k
  store(0, h0);
108
1.54k
  store(1, h1);
109
1.54k
  store(2, h2);
110
1.54k
  store(3, h3);
111
1.54k
  store(4, h4);
112
1.54k
  return digest;
113
1.54k
}
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
1.54k
inline std::string Base64Encode(const uint8_t *data, std::size_t len) {
120
1.54k
  static const char kTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
121
1.54k
  std::string out;
122
1.54k
  out.reserve(4 * ((len + 2) / 3));
123
12.3k
  for (std::size_t i = 0; i < len; i += 3) {
124
10.8k
    uint32_t n = static_cast<uint32_t>(data[i]) << 16;
125
10.8k
    if (i + 1 < len) n |= static_cast<uint32_t>(data[i + 1]) << 8;
126
10.8k
    if (i + 2 < len) n |= static_cast<uint32_t>(data[i + 2]);
127
10.8k
    out.push_back(kTable[(n >> 18) & 0x3F]);
128
10.8k
    out.push_back(kTable[(n >> 12) & 0x3F]);
129
10.8k
    out.push_back((i + 1 < len) ? kTable[(n >> 6) & 0x3F] : '=');
130
10.8k
    out.push_back((i + 2 < len) ? kTable[n & 0x3F] : '=');
131
10.8k
  }
132
1.54k
  return out;
133
1.54k
}
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
1.54k
inline std::string ComputeWebSocketAcceptKey(const std::string &key) {
141
1.54k
  static const char kGuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
142
1.54k
  std::string combined = key + kGuid;
143
1.54k
  auto digest = detail::Sha1(reinterpret_cast<const uint8_t *>(combined.data()), combined.size());
144
1.54k
  return detail::Base64Encode(digest.data(), digest.size());
145
1.54k
}
146
147
}  // namespace proto_fuzzer
148
149
#endif  // PROTO_FUZZER_WS_ACCEPT_KEY_H_