Everything the relay demands of a first packet can be checked without it, and checking late is what made this whole class of failure unreadable: a hello that breaks the contract is not refused, it is passed on to the domain the relay fronts for, and what comes back is an unsigned ServerHello an eternity later with nothing in it naming the cause. So the contract is now checked at the moment the hello is built. Length between the canonical floor and the relay's read limit. Declared record and handshake lengths equal to what was actually written. The extension block ending where the packet does, and the walk through the extensions landing on that end exactly - the fact parser leaves its loop as soon as fewer than four bytes remain, so a template stopping short of the block slipped past it. The first cipher suite after GREASE one of the three TLS 1.3 ones, with GREASE recognised by its shape rather than a table, because that is what the relay does with it. SNI equal to the domain the secret carries. The check warns, it does not refuse. A relay disagreeing with this list still gets its attempt, because a false alarm would take away a connection that works; what it buys is the cause named where it is created. That verdict is also what a digest mismatch is classified on now. The size of the answer cannot do it, however much the first logs suggested it could: the relay writes its own ServerHello either way and fills the first record with random bytes sized after the domain it fronts for, so against live relays a refusal came back in 184 bytes on one and in exactly the same 196 as an accepted handshake on the other, while an accepted one in front of a heavy domain ran to 3381. The hello we sent is the only thing that separates the two readings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.1 KiB
C++
65 lines
2.1 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#pragma once
|
|
|
|
#include "base/basic_types.h"
|
|
|
|
#include <QtCore/QByteArray>
|
|
#include <QtCore/QString>
|
|
#include <QtCore/QVector>
|
|
|
|
#include <optional>
|
|
|
|
namespace MTP::details {
|
|
|
|
struct ClientHelloFacts {
|
|
int legacyVersion = 0;
|
|
int tlsVersion = 0;
|
|
bool hasSni = false;
|
|
QByteArray sniHost;
|
|
QByteArray firstAlpn;
|
|
QVector<int> cipherSuites;
|
|
QVector<int> extensions;
|
|
QVector<int> signatureAlgorithms;
|
|
QVector<int> supportedVersions;
|
|
};
|
|
|
|
// Everything a relay checks before it accepts a ClientHello as its own. A
|
|
// hello that fails any of these is not answered with an error - the relay
|
|
// hands the connection to the site it fronts for, and the failure surfaces
|
|
// much later as an unsigned ServerHello. Checking the same list before the
|
|
// hello leaves means the log names the real cause instead of the symptom.
|
|
enum class ClientHelloContractIssue {
|
|
None,
|
|
BadRecordHeader, // not 16 03 01, or the record too short to qualify
|
|
TooShort, // under the canonical length the relay requires
|
|
TooLong, // over what the relay reads in one go
|
|
InconsistentLength, // declared record/handshake/extension sizes disagree
|
|
FirstCipherNotTls13, // first non-GREASE suite is not 1301/1302/1303
|
|
SniMissing,
|
|
SniMismatch, // not the domain carried in the secret
|
|
};
|
|
|
|
[[nodiscard]] ClientHelloContractIssue CheckClientHelloContract(
|
|
const QByteArray &hello,
|
|
const QByteArray &domainFromSecret);
|
|
|
|
// Stable slug for the diagnostics line - greppable, no spaces.
|
|
[[nodiscard]] QString ClientHelloContractIssueSlug(
|
|
ClientHelloContractIssue issue);
|
|
|
|
[[nodiscard]] bool IsClientHelloGrease(uint16 value);
|
|
|
|
[[nodiscard]] std::optional<ClientHelloFacts> ComputeClientHelloFacts(
|
|
const QByteArray &data);
|
|
|
|
[[nodiscard]] QString ComputeClientHelloJa4(const ClientHelloFacts &facts);
|
|
|
|
[[nodiscard]] QString ComputeClientHelloJa4(const QByteArray &data);
|
|
|
|
} // namespace MTP::details
|