567 lines
13 KiB
C++
567 lines
13 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/bytes.h"
|
|
#include "base/algorithm.h"
|
|
#include "base/basic_types.h"
|
|
|
|
extern "C" {
|
|
#include <openssl/bn.h>
|
|
#include <openssl/sha.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/aes.h>
|
|
#include <openssl/modes.h>
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/hmac.h>
|
|
} // extern "C"
|
|
|
|
#ifdef small
|
|
#undef small
|
|
#endif // small
|
|
|
|
namespace openssl {
|
|
|
|
class Context {
|
|
public:
|
|
Context() : _data(BN_CTX_new()) {
|
|
}
|
|
Context(const Context &other) = delete;
|
|
Context(Context &&other) : _data(base::take(other._data)) {
|
|
}
|
|
Context &operator=(const Context &other) = delete;
|
|
Context &operator=(Context &&other) {
|
|
_data = base::take(other._data);
|
|
return *this;
|
|
}
|
|
~Context() {
|
|
if (_data) {
|
|
BN_CTX_free(_data);
|
|
}
|
|
}
|
|
|
|
BN_CTX *raw() const {
|
|
return _data;
|
|
}
|
|
|
|
private:
|
|
BN_CTX *_data = nullptr;
|
|
|
|
};
|
|
|
|
class BigNum {
|
|
public:
|
|
BigNum() = default;
|
|
BigNum(const BigNum &other)
|
|
: _data((other.failed() || other.isZero())
|
|
? nullptr
|
|
: BN_dup(other.raw()))
|
|
, _failed(other._failed) {
|
|
}
|
|
BigNum(BigNum &&other)
|
|
: _data(std::exchange(other._data, nullptr))
|
|
, _failed(std::exchange(other._failed, false)) {
|
|
}
|
|
BigNum &operator=(const BigNum &other) {
|
|
if (other.failed()) {
|
|
_failed = true;
|
|
} else if (other.isZero()) {
|
|
clear();
|
|
_failed = false;
|
|
} else if (!_data) {
|
|
_data = BN_dup(other.raw());
|
|
_failed = false;
|
|
} else {
|
|
_failed = !BN_copy(raw(), other.raw());
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &operator=(BigNum &&other) {
|
|
std::swap(_data, other._data);
|
|
std::swap(_failed, other._failed);
|
|
return *this;
|
|
}
|
|
~BigNum() {
|
|
clear();
|
|
}
|
|
|
|
explicit BigNum(unsigned int word) : BigNum() {
|
|
setWord(word);
|
|
}
|
|
explicit BigNum(bytes::const_span bytes) : BigNum() {
|
|
setBytes(bytes);
|
|
}
|
|
|
|
BigNum &setWord(unsigned int word) {
|
|
if (!word) {
|
|
clear();
|
|
_failed = false;
|
|
} else {
|
|
_failed = !BN_set_word(raw(), word);
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &setBytes(bytes::const_span bytes) {
|
|
if (bytes.empty()) {
|
|
clear();
|
|
_failed = false;
|
|
} else {
|
|
_failed = !BN_bin2bn(
|
|
reinterpret_cast<const unsigned char*>(bytes.data()),
|
|
bytes.size(),
|
|
raw());
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
BigNum &setAdd(const BigNum &a, const BigNum &b) {
|
|
if (a.failed() || b.failed()) {
|
|
_failed = true;
|
|
} else {
|
|
_failed = !BN_add(raw(), a.raw(), b.raw());
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &setSub(const BigNum &a, const BigNum &b) {
|
|
if (a.failed() || b.failed()) {
|
|
_failed = true;
|
|
} else {
|
|
_failed = !BN_sub(raw(), a.raw(), b.raw());
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &setMul(
|
|
const BigNum &a,
|
|
const BigNum &b,
|
|
const Context &context = Context()) {
|
|
if (a.failed() || b.failed()) {
|
|
_failed = true;
|
|
} else {
|
|
_failed = !BN_mul(raw(), a.raw(), b.raw(), context.raw());
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &setModAdd(
|
|
const BigNum &a,
|
|
const BigNum &b,
|
|
const BigNum &m,
|
|
const Context &context = Context()) {
|
|
if (a.failed() || b.failed() || m.failed()) {
|
|
_failed = true;
|
|
} else if (a.isNegative() || b.isNegative() || m.isNegative()) {
|
|
_failed = true;
|
|
} else if (!BN_mod_add(raw(), a.raw(), b.raw(), m.raw(), context.raw())) {
|
|
_failed = true;
|
|
} else if (isNegative()) {
|
|
_failed = true;
|
|
} else {
|
|
_failed = false;
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &setModSub(
|
|
const BigNum &a,
|
|
const BigNum &b,
|
|
const BigNum &m,
|
|
const Context &context = Context()) {
|
|
if (a.failed() || b.failed() || m.failed()) {
|
|
_failed = true;
|
|
} else if (a.isNegative() || b.isNegative() || m.isNegative()) {
|
|
_failed = true;
|
|
} else if (!BN_mod_sub(raw(), a.raw(), b.raw(), m.raw(), context.raw())) {
|
|
_failed = true;
|
|
} else if (isNegative()) {
|
|
_failed = true;
|
|
} else {
|
|
_failed = false;
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &setModMul(
|
|
const BigNum &a,
|
|
const BigNum &b,
|
|
const BigNum &m,
|
|
const Context &context = Context()) {
|
|
if (a.failed() || b.failed() || m.failed()) {
|
|
_failed = true;
|
|
} else if (a.isNegative() || b.isNegative() || m.isNegative()) {
|
|
_failed = true;
|
|
} else if (!BN_mod_mul(raw(), a.raw(), b.raw(), m.raw(), context.raw())) {
|
|
_failed = true;
|
|
} else if (isNegative()) {
|
|
_failed = true;
|
|
} else {
|
|
_failed = false;
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &setModInverse(
|
|
const BigNum &a,
|
|
const BigNum &m,
|
|
const Context &context = Context()) {
|
|
if (a.failed() || m.failed()) {
|
|
_failed = true;
|
|
} else if (a.isNegative() || m.isNegative()) {
|
|
_failed = true;
|
|
} else if (!BN_mod_inverse(raw(), a.raw(), m.raw(), context.raw())) {
|
|
_failed = true;
|
|
} else if (isNegative()) {
|
|
_failed = true;
|
|
} else {
|
|
_failed = false;
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &setModExp(
|
|
const BigNum &base,
|
|
const BigNum &power,
|
|
const BigNum &m,
|
|
const Context &context = Context()) {
|
|
if (base.failed() || power.failed() || m.failed()) {
|
|
_failed = true;
|
|
} else if (base.isNegative() || power.isNegative() || m.isNegative()) {
|
|
_failed = true;
|
|
} else if (!BN_mod_exp(raw(), base.raw(), power.raw(), m.raw(), context.raw())) {
|
|
_failed = true;
|
|
} else if (isNegative()) {
|
|
_failed = true;
|
|
} else {
|
|
_failed = false;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
[[nodiscard]] bool isZero() const {
|
|
return !failed() && (!_data || BN_is_zero(raw()));
|
|
}
|
|
|
|
[[nodiscard]] bool isOne() const {
|
|
return !failed() && _data && BN_is_one(raw());
|
|
}
|
|
|
|
[[nodiscard]] bool isNegative() const {
|
|
return !failed() && _data && BN_is_negative(raw());
|
|
}
|
|
|
|
[[nodiscard]] bool isPrime(const Context &context = Context()) const {
|
|
if (failed() || !_data) {
|
|
return false;
|
|
}
|
|
constexpr auto kMillerRabinIterationCount = 30;
|
|
const auto result = BN_is_prime_ex(
|
|
raw(),
|
|
kMillerRabinIterationCount,
|
|
context.raw(),
|
|
nullptr);
|
|
if (result == 1) {
|
|
return true;
|
|
} else if (result != 0) {
|
|
_failed = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
BigNum &subWord(unsigned int word) {
|
|
if (failed()) {
|
|
return *this;
|
|
} else if (!BN_sub_word(raw(), word)) {
|
|
_failed = true;
|
|
}
|
|
return *this;
|
|
}
|
|
BigNum &divWord(BN_ULONG word, BN_ULONG *mod = nullptr) {
|
|
Expects(word != 0);
|
|
|
|
const auto result = failed()
|
|
? (BN_ULONG)-1
|
|
: BN_div_word(raw(), word);
|
|
if (result == (BN_ULONG)-1) {
|
|
_failed = true;
|
|
}
|
|
if (mod) {
|
|
*mod = result;
|
|
}
|
|
return *this;
|
|
}
|
|
[[nodiscard]] BN_ULONG countModWord(BN_ULONG word) const {
|
|
Expects(word != 0);
|
|
|
|
return failed() ? (BN_ULONG)-1 : BN_mod_word(raw(), word);
|
|
}
|
|
|
|
[[nodiscard]] int bitsSize() const {
|
|
return failed() ? 0 : BN_num_bits(raw());
|
|
}
|
|
[[nodiscard]] int bytesSize() const {
|
|
return failed() ? 0 : BN_num_bytes(raw());
|
|
}
|
|
|
|
[[nodiscard]] bytes::vector getBytes() const {
|
|
if (failed()) {
|
|
return {};
|
|
}
|
|
auto length = BN_num_bytes(raw());
|
|
auto result = bytes::vector(length);
|
|
auto resultSize = BN_bn2bin(
|
|
raw(),
|
|
reinterpret_cast<unsigned char*>(result.data()));
|
|
Assert(resultSize == length);
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] BIGNUM *raw() {
|
|
if (!_data) _data = BN_new();
|
|
return _data;
|
|
}
|
|
[[nodiscard]] const BIGNUM *raw() const {
|
|
if (!_data) _data = BN_new();
|
|
return _data;
|
|
}
|
|
[[nodiscard]] BIGNUM *takeRaw() {
|
|
return _failed
|
|
? nullptr
|
|
: _data
|
|
? std::exchange(_data, nullptr)
|
|
: BN_new();
|
|
}
|
|
|
|
[[nodiscard]] bool failed() const {
|
|
return _failed;
|
|
}
|
|
|
|
[[nodiscard]] static BigNum Add(const BigNum &a, const BigNum &b) {
|
|
return BigNum().setAdd(a, b);
|
|
}
|
|
[[nodiscard]] static BigNum Sub(const BigNum &a, const BigNum &b) {
|
|
return BigNum().setSub(a, b);
|
|
}
|
|
[[nodiscard]] static BigNum Mul(
|
|
const BigNum &a,
|
|
const BigNum &b,
|
|
const Context &context = Context()) {
|
|
return BigNum().setMul(a, b, context);
|
|
}
|
|
[[nodiscard]] static BigNum ModAdd(
|
|
const BigNum &a,
|
|
const BigNum &b,
|
|
const BigNum &mod,
|
|
const Context &context = Context()) {
|
|
return BigNum().setModAdd(a, b, mod, context);
|
|
}
|
|
[[nodiscard]] static BigNum ModSub(
|
|
const BigNum &a,
|
|
const BigNum &b,
|
|
const BigNum &mod,
|
|
const Context &context = Context()) {
|
|
return BigNum().setModSub(a, b, mod, context);
|
|
}
|
|
[[nodiscard]] static BigNum ModMul(
|
|
const BigNum &a,
|
|
const BigNum &b,
|
|
const BigNum &mod,
|
|
const Context &context = Context()) {
|
|
return BigNum().setModMul(a, b, mod, context);
|
|
}
|
|
[[nodiscard]] static BigNum ModInverse(
|
|
const BigNum &a,
|
|
const BigNum &mod,
|
|
const Context &context = Context()) {
|
|
return BigNum().setModInverse(a, mod, context);
|
|
}
|
|
[[nodiscard]] static BigNum ModExp(
|
|
const BigNum &base,
|
|
const BigNum &power,
|
|
const BigNum &mod,
|
|
const Context &context = Context()) {
|
|
return BigNum().setModExp(base, power, mod, context);
|
|
}
|
|
[[nodiscard]] static BigNum Failed() {
|
|
auto result = BigNum();
|
|
result._failed = true;
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
void clear() {
|
|
BN_clear_free(std::exchange(_data, nullptr));
|
|
}
|
|
|
|
mutable BIGNUM *_data = nullptr;
|
|
mutable bool _failed = false;
|
|
|
|
};
|
|
|
|
namespace details {
|
|
|
|
template <typename Context, typename Method, typename Arg>
|
|
inline void ShaUpdate(Context context, Method method, Arg &&arg) {
|
|
const auto span = bytes::make_span(arg);
|
|
method(context, span.data(), span.size());
|
|
}
|
|
|
|
template <typename Context, typename Method, typename Arg, typename ...Args>
|
|
inline void ShaUpdate(Context context, Method method, Arg &&arg, Args &&...args) {
|
|
const auto span = bytes::make_span(arg);
|
|
method(context, span.data(), span.size());
|
|
ShaUpdate(context, method, args...);
|
|
}
|
|
|
|
template <size_type Size, typename Method>
|
|
inline bytes::vector Sha(Method method, bytes::const_span data) {
|
|
auto result = bytes::vector(Size);
|
|
method(
|
|
reinterpret_cast<const unsigned char*>(data.data()),
|
|
data.size(),
|
|
reinterpret_cast<unsigned char*>(result.data()));
|
|
return result;
|
|
}
|
|
|
|
template <
|
|
size_type Size,
|
|
typename Context,
|
|
typename Init,
|
|
typename Update,
|
|
typename Finalize,
|
|
typename ...Args,
|
|
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
|
|
bytes::vector Sha(
|
|
Context context,
|
|
Init init,
|
|
Update update,
|
|
Finalize finalize,
|
|
Args &&...args) {
|
|
auto result = bytes::vector(Size);
|
|
|
|
init(&context);
|
|
ShaUpdate(&context, update, args...);
|
|
finalize(reinterpret_cast<unsigned char*>(result.data()), &context);
|
|
|
|
return result;
|
|
}
|
|
|
|
template <
|
|
size_type Size,
|
|
typename Evp>
|
|
bytes::vector Pbkdf2(
|
|
bytes::const_span password,
|
|
bytes::const_span salt,
|
|
int iterations,
|
|
Evp evp) {
|
|
auto result = bytes::vector(Size);
|
|
PKCS5_PBKDF2_HMAC(
|
|
reinterpret_cast<const char*>(password.data()),
|
|
password.size(),
|
|
reinterpret_cast<const unsigned char*>(salt.data()),
|
|
salt.size(),
|
|
iterations,
|
|
evp,
|
|
result.size(),
|
|
reinterpret_cast<unsigned char*>(result.data()));
|
|
return result;
|
|
}
|
|
|
|
} // namespace details
|
|
|
|
constexpr auto kSha1Size = size_type(SHA_DIGEST_LENGTH);
|
|
constexpr auto kSha256Size = size_type(SHA256_DIGEST_LENGTH);
|
|
constexpr auto kSha512Size = size_type(SHA512_DIGEST_LENGTH);
|
|
|
|
inline bytes::vector Sha1(bytes::const_span data) {
|
|
return details::Sha<kSha1Size>(SHA1, data);
|
|
}
|
|
|
|
template <
|
|
typename ...Args,
|
|
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
|
|
inline bytes::vector Sha1(Args &&...args) {
|
|
return details::Sha<kSha1Size>(
|
|
SHA_CTX(),
|
|
SHA1_Init,
|
|
SHA1_Update,
|
|
SHA1_Final,
|
|
args...);
|
|
}
|
|
|
|
inline bytes::vector Sha256(bytes::const_span data) {
|
|
return details::Sha<kSha256Size>(SHA256, data);
|
|
}
|
|
|
|
template <
|
|
typename ...Args,
|
|
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
|
|
inline bytes::vector Sha256(Args &&...args) {
|
|
return details::Sha<kSha256Size>(
|
|
SHA256_CTX(),
|
|
SHA256_Init,
|
|
SHA256_Update,
|
|
SHA256_Final,
|
|
args...);
|
|
}
|
|
|
|
inline bytes::vector Sha512(bytes::const_span data) {
|
|
return details::Sha<kSha512Size>(SHA512, data);
|
|
}
|
|
|
|
template <
|
|
typename ...Args,
|
|
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
|
|
inline bytes::vector Sha512(Args &&...args) {
|
|
return details::Sha<kSha512Size>(
|
|
SHA512_CTX(),
|
|
SHA512_Init,
|
|
SHA512_Update,
|
|
SHA512_Final,
|
|
args...);
|
|
}
|
|
|
|
inline void AddRandomSeed(bytes::const_span data) {
|
|
RAND_seed(data.data(), data.size());
|
|
}
|
|
|
|
inline bytes::vector Pbkdf2Sha512(
|
|
bytes::const_span password,
|
|
bytes::const_span salt,
|
|
int iterations) {
|
|
return details::Pbkdf2<kSha512Size>(
|
|
password,
|
|
salt,
|
|
iterations,
|
|
EVP_sha512());
|
|
}
|
|
|
|
inline bytes::vector HmacSha256(
|
|
bytes::const_span key,
|
|
bytes::const_span data) {
|
|
auto result = bytes::vector(kSha256Size);
|
|
auto length = (unsigned int)kSha256Size;
|
|
|
|
HMAC(
|
|
EVP_sha256(),
|
|
key.data(),
|
|
key.size(),
|
|
reinterpret_cast<const unsigned char*>(data.data()),
|
|
data.size(),
|
|
reinterpret_cast<unsigned char*>(result.data()),
|
|
&length);
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace openssl
|
|
|
|
namespace bytes {
|
|
|
|
inline void set_random(span destination) {
|
|
RAND_bytes(
|
|
reinterpret_cast<unsigned char*>(destination.data()),
|
|
destination.size());
|
|
}
|
|
|
|
} // namespace bytes
|