ZaStoGram_desktop/Telegram/SourceFiles/webauthn/cable_core.h

322 lines
8.5 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 <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <variant>
#include <vector>
typedef struct ec_key_st EC_KEY;
namespace Platform::WebAuthn::Cable {
using Bytes = std::vector<uint8_t>;
using ByteSpan = std::span<const uint8_t>;
inline constexpr auto kP256X962Length = size_t(65);
inline constexpr auto kCompressedPublicKeySize = size_t(33);
inline constexpr auto kQRSecretSize = size_t(16);
inline constexpr auto kAdvertSize = size_t(20);
inline constexpr auto kEidSize = size_t(16);
inline constexpr auto kEidKeySize = size_t(64);
inline constexpr auto kNonceSize = size_t(10);
inline constexpr auto kRoutingIdSize = size_t(3);
inline constexpr auto kTunnelIdSize = size_t(16);
inline constexpr auto kPskSize = size_t(32);
inline constexpr auto kHandshakeResponseSize = kP256X962Length + 16;
enum class MessageType : uint8_t {
Shutdown = 0,
Ctap = 1,
Update = 2,
Json = 3,
};
class CborValue;
using CborArray = std::vector<CborValue>;
using CborMap = std::vector<std::pair<CborValue, CborValue>>;
class CborValue final {
public:
CborValue(int64_t value) : _data(value) {
}
CborValue(int value) : _data(int64_t(value)) {
}
CborValue(Bytes value) : _data(std::move(value)) {
}
CborValue(ByteSpan value) : _data(Bytes(value.begin(), value.end())) {
}
CborValue(std::string value) : _data(std::move(value)) {
}
CborValue(const char *value) : _data(std::string(value)) {
}
CborValue(CborArray value) : _data(std::move(value)) {
}
CborValue(CborMap value) : _data(std::move(value)) {
}
CborValue(bool value) : _data(value) {
}
[[nodiscard]] bool isInt() const {
return std::holds_alternative<int64_t>(_data);
}
[[nodiscard]] bool isBytes() const {
return std::holds_alternative<Bytes>(_data);
}
[[nodiscard]] bool isString() const {
return std::holds_alternative<std::string>(_data);
}
[[nodiscard]] bool isArray() const {
return std::holds_alternative<CborArray>(_data);
}
[[nodiscard]] bool isMap() const {
return std::holds_alternative<CborMap>(_data);
}
[[nodiscard]] bool isBool() const {
return std::holds_alternative<bool>(_data);
}
[[nodiscard]] int64_t toInt() const {
return std::get<int64_t>(_data);
}
[[nodiscard]] const Bytes &bytes() const {
return std::get<Bytes>(_data);
}
[[nodiscard]] const std::string &string() const {
return std::get<std::string>(_data);
}
[[nodiscard]] const CborArray &array() const {
return std::get<CborArray>(_data);
}
[[nodiscard]] const CborMap &map() const {
return std::get<CborMap>(_data);
}
[[nodiscard]] bool toBool() const {
return std::get<bool>(_data);
}
[[nodiscard]] const CborValue *find(int64_t key) const;
private:
std::variant<int64_t, Bytes, std::string, CborArray, CborMap, bool> _data;
};
[[nodiscard]] Bytes CborEncode(const CborValue &value);
[[nodiscard]] std::optional<CborValue> CborDecode(ByteSpan data);
struct EcKeyDeleter {
void operator()(EC_KEY *key) const;
};
using EcKeyPtr = std::unique_ptr<EC_KEY, EcKeyDeleter>;
[[nodiscard]] bool RandomBytes(uint8_t *out, size_t size);
[[nodiscard]] std::array<uint8_t, 20> Sha1Digest(ByteSpan data);
[[nodiscard]] std::array<uint8_t, 32> Sha256Digest(ByteSpan data);
[[nodiscard]] std::array<uint8_t, 32> HmacSha256(
ByteSpan key,
ByteSpan data);
void HkdfSha256(
uint8_t *out,
size_t outLength,
ByteSpan secret,
ByteSpan salt,
ByteSpan info);
enum class DerivedValueType : uint32_t {
EidKey = 1,
TunnelId = 2,
Psk = 3,
};
template <size_t Size>
[[nodiscard]] std::array<uint8_t, Size> Derive(
ByteSpan secret,
ByteSpan nonce,
DerivedValueType type) {
const auto type32 = uint32_t(type);
const auto info = std::array<uint8_t, 4>{
uint8_t(type32 & 0xFF),
uint8_t((type32 >> 8) & 0xFF),
uint8_t((type32 >> 16) & 0xFF),
uint8_t((type32 >> 24) & 0xFF),
};
auto result = std::array<uint8_t, Size>();
HkdfSha256(result.data(), Size, secret, nonce, info);
return result;
}
[[nodiscard]] EcKeyPtr GenerateP256Key();
[[nodiscard]] Bytes PublicKeyX962(EC_KEY *key, bool compressed);
[[nodiscard]] std::optional<std::array<uint8_t, 32>> EcdhSharedX(
EC_KEY *privateKey,
ByteSpan peerX962);
[[nodiscard]] Bytes AesGcmSeal(
ByteSpan key32,
ByteSpan nonce12,
ByteSpan plaintext,
ByteSpan additionalData);
[[nodiscard]] std::optional<Bytes> AesGcmOpen(
ByteSpan key32,
ByteSpan nonce12,
ByteSpan ciphertext,
ByteSpan additionalData);
class Noise final {
public:
enum class HandshakeType {
KNpsk0,
NKpsk0,
};
void init(HandshakeType type);
void mixHash(ByteSpan in);
void mixHashPoint(ByteSpan pointX962);
void mixKey(ByteSpan ikm);
void mixKeyAndHash(ByteSpan ikm);
[[nodiscard]] Bytes encryptAndHash(ByteSpan plaintext);
[[nodiscard]] std::optional<Bytes> decryptAndHash(ByteSpan ciphertext);
[[nodiscard]] auto trafficKeys() const
-> std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32>>;
[[nodiscard]] std::array<uint8_t, 32> handshakeHash() const {
return _h;
}
private:
void initializeKey(ByteSpan key32);
std::array<uint8_t, 32> _chainingKey = {};
std::array<uint8_t, 32> _h = {};
std::array<uint8_t, 32> _symmetricKey = {};
uint32_t _symmetricNonce = 0;
};
class Crypter final {
public:
Crypter(ByteSpan readKey32, ByteSpan writeKey32);
[[nodiscard]] std::optional<Bytes> encrypt(ByteSpan plaintext);
[[nodiscard]] std::optional<Bytes> decrypt(ByteSpan ciphertext);
private:
std::array<uint8_t, 32> _readKey = {};
std::array<uint8_t, 32> _writeKey = {};
uint32_t _readSequence = 0;
uint32_t _writeSequence = 0;
};
struct HandshakeResult {
std::unique_ptr<Crypter> crypter;
std::array<uint8_t, 32> handshakeHash = {};
};
class HandshakeInitiator final {
public:
HandshakeInitiator(ByteSpan psk32, EC_KEY *localIdentity);
[[nodiscard]] Bytes buildInitialMessage();
[[nodiscard]] std::optional<HandshakeResult> processResponse(
ByteSpan response);
private:
std::array<uint8_t, 32> _psk = {};
EC_KEY *_localIdentity = nullptr;
EcKeyPtr _ephemeralKey;
Noise _noise;
};
[[nodiscard]] std::optional<HandshakeResult> RespondToHandshake(
ByteSpan psk32,
ByteSpan peerIdentityX962,
ByteSpan message,
Bytes *outResponse);
struct QRKey {
EcKeyPtr identity;
std::array<uint8_t, kQRSecretSize> secret = {};
};
[[nodiscard]] QRKey MakeQRKey();
[[nodiscard]] std::string EncodeQRContents(
const QRKey &key,
bool makeCredentialHint,
int64_t nowUnixSeconds);
[[nodiscard]] std::string BytesToDigits(ByteSpan bytes);
[[nodiscard]] std::optional<Bytes> DigitsToBytes(const std::string &digits);
struct EidComponents {
std::array<uint8_t, kNonceSize> nonce = {};
std::array<uint8_t, kRoutingIdSize> routingId = {};
uint16_t tunnelServerDomain = 0;
};
[[nodiscard]] std::optional<std::array<uint8_t, kEidSize>> DecryptAdvert(
ByteSpan serviceData,
ByteSpan eidKey64);
[[nodiscard]] EidComponents ToEidComponents(
const std::array<uint8_t, kEidSize> &eid);
[[nodiscard]] std::string DecodeTunnelServerDomain(uint16_t domain);
[[nodiscard]] std::string HexLower(ByteSpan bytes);
[[nodiscard]] Bytes BuildMakeCredentialRequest(
ByteSpan clientDataHash32,
const std::string &rpId,
const std::string &rpName,
ByteSpan userId,
const std::string &userName,
const std::string &userDisplayName,
const std::vector<int> &algorithms);
[[nodiscard]] Bytes BuildGetAssertionRequest(
const std::string &rpId,
ByteSpan clientDataHash32,
const std::vector<Bytes> &allowCredentialIds);
struct MakeCredentialResponse {
Bytes authData;
Bytes credentialId;
};
struct GetAssertionResponse {
Bytes credentialId;
Bytes authData;
Bytes signature;
Bytes userHandle;
};
inline constexpr auto kCtapSuccess = uint8_t(0);
inline constexpr auto kCtapErrOperationDenied = uint8_t(0x27);
inline constexpr auto kCtapErrNoCredentials = uint8_t(0x2E);
inline constexpr auto kCtapErrUserActionTimeout = uint8_t(0x2F);
[[nodiscard]] std::optional<MakeCredentialResponse>
ParseMakeCredentialResponse(ByteSpan payloadWithStatus);
[[nodiscard]] std::optional<GetAssertionResponse> ParseGetAssertionResponse(
ByteSpan payloadWithStatus);
[[nodiscard]] std::optional<Bytes> CredentialIdFromAuthData(
ByteSpan authData);
struct PostHandshakeMessage {
Bytes getInfo;
int protocolRevision = 1;
bool supportsCtap = true;
};
[[nodiscard]] std::optional<PostHandshakeMessage> ParsePostHandshakeMessage(
ByteSpan plaintext);
} // namespace Platform::WebAuthn::Cable