diff options
| author | levlam <levlam@telegram.org> | 2025-02-11 18:41:15 +0300 |
|---|---|---|
| committer | levlam <levlam@telegram.org> | 2025-02-11 18:41:15 +0300 |
| commit | b034afecfc662d77914f44e081f7a67dcc5ef160 (patch) | |
| tree | 578da6a597d26001a304e85415f039ca51a826f4 /tdutils | |
| parent | c3bd38bdc8f8d95496c347ce5d643bf275e5d799 (diff) | |
Add Ed25519.
Diffstat (limited to 'tdutils')
| -rw-r--r-- | tdutils/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | tdutils/td/utils/Ed25519.cpp | 343 | ||||
| -rw-r--r-- | tdutils/td/utils/Ed25519.h | 78 |
3 files changed, 423 insertions, 0 deletions
diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt index eed7980e4..f831ba42e 100644 --- a/tdutils/CMakeLists.txt +++ b/tdutils/CMakeLists.txt @@ -98,6 +98,7 @@ set(TDUTILS_SOURCE td/utils/BufferedUdp.cpp td/utils/check.cpp td/utils/crypto.cpp + td/utils/Ed25519.cpp td/utils/emoji.cpp td/utils/ExitGuard.cpp td/utils/FileLog.cpp @@ -208,6 +209,7 @@ set(TDUTILS_SOURCE td/utils/crypto.h td/utils/DecTree.h td/utils/Destructor.h + td/utils/Ed25519.h td/utils/emoji.h td/utils/Enumerator.h td/utils/EpochBasedMemoryReclamation.h diff --git a/tdutils/td/utils/Ed25519.cpp b/tdutils/td/utils/Ed25519.cpp new file mode 100644 index 000000000..4465f4de2 --- /dev/null +++ b/tdutils/td/utils/Ed25519.cpp @@ -0,0 +1,343 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/Ed25519.h" + +#include "td/utils/BigNum.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/ScopeGuard.h" + +#include <openssl/evp.h> +#include <openssl/opensslv.h> +#include <openssl/pem.h> +#include <openssl/x509.h> + +namespace td { + +Ed25519::PublicKey::PublicKey(SecureString octet_string) : octet_string_(std::move(octet_string)) { +} + +SecureString Ed25519::PublicKey::as_octet_string() const { + return octet_string_.copy(); +} + +Ed25519::PrivateKey::PrivateKey(SecureString octet_string) : octet_string_(std::move(octet_string)) { +} + +SecureString Ed25519::PrivateKey::as_octet_string() const { + return octet_string_.copy(); +} + +#if OPENSSL_VERSION_NUMBER >= 0x10101000L +namespace detail { + +static Result<SecureString> X25519_key_from_PKEY(EVP_PKEY *pkey, bool is_private) { + auto func = is_private ? &EVP_PKEY_get_raw_private_key : &EVP_PKEY_get_raw_public_key; + size_t len = 0; + if (func(pkey, nullptr, &len) == 0) { + return Status::Error("Failed to get raw key length"); + } + CHECK(len == 32); + + SecureString result(len); + if (func(pkey, result.as_mutable_slice().ubegin(), &len) == 0) { + return Status::Error("Failed to get raw key"); + } + return std::move(result); +} + +static EVP_PKEY *X25519_key_to_PKEY(Slice key, bool is_private) { + auto func = is_private ? &EVP_PKEY_new_raw_private_key : &EVP_PKEY_new_raw_public_key; + return func(EVP_PKEY_ED25519, nullptr, key.ubegin(), key.size()); +} + +static Result<SecureString> X25519_pem_from_PKEY(EVP_PKEY *pkey, bool is_private, Slice password) { + BIO *mem_bio = BIO_new(BIO_s_mem()); + SCOPE_EXIT { + BIO_vfree(mem_bio); + }; + if (is_private) { + PEM_write_bio_PrivateKey(mem_bio, pkey, EVP_aes_256_cbc(), const_cast<unsigned char *>(password.ubegin()), + narrow_cast<int>(password.size()), nullptr, nullptr); + } else { + PEM_write_bio_PUBKEY(mem_bio, pkey); + } + char *data_ptr = nullptr; + auto data_size = BIO_get_mem_data(mem_bio, &data_ptr); + return std::string(data_ptr, data_size); +} + +static int password_cb(char *buf, int size, int rwflag, void *u) { + auto &password = *reinterpret_cast<Slice *>(u); + auto password_size = narrow_cast<int>(password.size()); + if (size < password_size) { + return -1; + } + if (rwflag == 0) { + MutableSlice(buf, size).copy_from(password); + } + return password_size; +} + +static EVP_PKEY *X25519_pem_to_PKEY(Slice pem, Slice password) { + BIO *mem_bio = BIO_new_mem_buf(pem.ubegin(), narrow_cast<int>(pem.size())); + SCOPE_EXIT { + BIO_vfree(mem_bio); + }; + + return PEM_read_bio_PrivateKey(mem_bio, nullptr, password_cb, &password); +} + +} // namespace detail +#endif + +Result<Ed25519::PrivateKey> Ed25519::generate_private_key() { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(NID_ED25519, nullptr); + if (pctx == nullptr) { + return Status::Error("Can't create EVP_PKEY_CTX"); + } + SCOPE_EXIT { + EVP_PKEY_CTX_free(pctx); + }; + + if (EVP_PKEY_keygen_init(pctx) <= 0) { + return Status::Error("Can't init keygen"); + } + + EVP_PKEY *pkey = nullptr; + if (EVP_PKEY_keygen(pctx, &pkey) <= 0) { + return Status::Error("Can't generate random private key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey); + }; + + TRY_RESULT(private_key, detail::X25519_key_from_PKEY(pkey, true)); + return std::move(private_key); +#else + return Status::Error("Unsupported"); +#endif +} + +Result<Ed25519::PublicKey> Ed25519::PrivateKey::get_public_key() const { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + auto pkey = detail::X25519_key_to_PKEY(octet_string_, true); + if (pkey == nullptr) { + return Status::Error("Can't import private key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey); + }; + + TRY_RESULT(key, detail::X25519_key_from_PKEY(pkey, false)); + return Ed25519::PublicKey(std::move(key)); +#else + return Status::Error("Unsupported"); +#endif +} + +Result<SecureString> Ed25519::PrivateKey::as_pem(Slice password) const { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + auto pkey = detail::X25519_key_to_PKEY(octet_string_, true); + if (pkey == nullptr) { + return Status::Error("Can't import private key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey); + }; + + return detail::X25519_pem_from_PKEY(pkey, true, password); +#else + return Status::Error("Unsupported"); +#endif +} + +Result<Ed25519::PrivateKey> Ed25519::PrivateKey::from_pem(Slice pem, Slice password) { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + auto pkey = detail::X25519_pem_to_PKEY(pem, password); + if (pkey == nullptr) { + return Status::Error("Can't import private key from pem"); + } + TRY_RESULT(key, detail::X25519_key_from_PKEY(pkey, true)); + return Ed25519::PrivateKey(std::move(key)); +#else + return Status::Error("Unsupported"); +#endif +} + +Result<SecureString> Ed25519::PrivateKey::sign(Slice data) const { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + auto pkey = detail::X25519_key_to_PKEY(octet_string_, true); + if (pkey == nullptr) { + return Status::Error("Can't import private key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey); + }; + + EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); + if (md_ctx == nullptr) { + return Status::Error("Can't create EVP_MD_CTX"); + } + SCOPE_EXIT { + EVP_MD_CTX_free(md_ctx); + }; + + if (EVP_DigestSignInit(md_ctx, nullptr, nullptr, nullptr, pkey) <= 0) { + return Status::Error("Can't init DigestSign"); + } + + SecureString res(64, '\0'); + size_t len = 64; + if (EVP_DigestSign(md_ctx, res.as_mutable_slice().ubegin(), &len, data.ubegin(), data.size()) <= 0) { + return Status::Error("Can't sign data"); + } + return std::move(res); +#else + return Status::Error("Unsupported"); +#endif +} + +Status Ed25519::PublicKey::verify_signature(Slice data, Slice signature) const { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + auto pkey = detail::X25519_key_to_PKEY(octet_string_, false); + if (pkey == nullptr) { + return Status::Error("Can't import public key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey); + }; + + EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); + if (md_ctx == nullptr) { + return Status::Error("Can't create EVP_MD_CTX"); + } + SCOPE_EXIT { + EVP_MD_CTX_free(md_ctx); + }; + + if (EVP_DigestVerifyInit(md_ctx, nullptr, nullptr, nullptr, pkey) <= 0) { + return Status::Error("Can't init DigestVerify"); + } + + if (EVP_DigestVerify(md_ctx, signature.ubegin(), signature.size(), data.ubegin(), data.size())) { + return Status::OK(); + } + return Status::Error("Wrong signature"); +#else + return Status::Error("Unsupported"); +#endif +} + +Result<SecureString> Ed25519::compute_shared_secret(const PublicKey &public_key, const PrivateKey &private_key) { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + BigNum p = BigNum::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed").move_as_ok(); + auto public_y = public_key.as_octet_string(); + public_y.as_mutable_slice()[31] = static_cast<char>(public_y[31] & 127); + BigNum y = BigNum::from_le_binary(public_y); + BigNum y2 = y.clone(); + y += 1; + y2 -= 1; + + BigNumContext context; + + BigNum::mod_sub(y2, p, y2, p, context); + + BigNum inverse_y_plus_1; + BigNum::mod_inverse(inverse_y_plus_1, y2, p, context); + + BigNum u; + BigNum::mod_mul(u, y, inverse_y_plus_1, p, context); + + auto pr_key = private_key.as_octet_string(); + unsigned char buf[64]; + SHA512(Slice(pr_key).ubegin(), 32, buf); + buf[0] &= 248; + buf[31] &= 127; + buf[31] |= 64; + + auto pkey_private = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, nullptr, buf, 32); + if (pkey_private == nullptr) { + return Status::Error("Can't import private key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey_private); + }; + // LOG(ERROR) << buffer_to_hex(Slice(buf, 32)); + + auto pub_key = u.to_le_binary(32); + auto pkey_public = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, nullptr, Slice(pub_key).ubegin(), pub_key.size()); + if (pkey_public == nullptr) { + return Status::Error("Can't import public key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey_public); + }; + // LOG(ERROR) << buffer_to_hex(pub_key); + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey_private, nullptr); + if (ctx == nullptr) { + return Status::Error("Can't create EVP_PKEY_CTX"); + } + SCOPE_EXIT { + EVP_PKEY_CTX_free(ctx); + }; + + if (EVP_PKEY_derive_init(ctx) <= 0) { + return Status::Error("Can't init derive"); + } + if (EVP_PKEY_derive_set_peer(ctx, pkey_public) <= 0) { + return Status::Error("Can't init derive"); + } + + size_t result_len = 0; + if (EVP_PKEY_derive(ctx, nullptr, &result_len) <= 0) { + return Status::Error("Can't get result length"); + } + if (result_len != 32) { + return Status::Error("Unexpected result length"); + } + + SecureString result(result_len, '\0'); + if (EVP_PKEY_derive(ctx, result.as_mutable_slice().ubegin(), &result_len) <= 0) { + return Status::Error("Failed to compute shared secret"); + } + return std::move(result); +#else + return Status::Error("Unsupported"); +#endif +} + +Result<SecureString> Ed25519::get_public_key(Slice private_key) { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + auto pkey_private = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, nullptr, private_key.ubegin(), 32); + if (pkey_private == nullptr) { + return Status::Error("Invalid X25520 private key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey_private); + }; + + auto func = &EVP_PKEY_get_raw_public_key; + size_t len = 0; + if (func(pkey_private, nullptr, &len) == 0) { + return Status::Error("Failed to get raw key length"); + } + CHECK(len == 32); + + SecureString result(len); + if (func(pkey_private, result.as_mutable_slice().ubegin(), &len) == 0) { + return Status::Error("Failed to get raw key"); + } + return std::move(result); +#else + return Status::Error("Unsupported"); +#endif +} + +} // namespace td diff --git a/tdutils/td/utils/Ed25519.h b/tdutils/td/utils/Ed25519.h new file mode 100644 index 000000000..1f458d321 --- /dev/null +++ b/tdutils/td/utils/Ed25519.h @@ -0,0 +1,78 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/SharedSlice.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" + +#if TD_HAVE_OPENSSL + +namespace td { + +class Ed25519 { + public: + class PublicKey { + public: + static constexpr size_t LENGTH = 32; + + PublicKey() = default; + explicit PublicKey(SecureString octet_string); + PublicKey(const PublicKey &other) : octet_string_(other.octet_string_.copy()) { + } + PublicKey(PublicKey &&) noexcept = default; + PublicKey &operator=(const PublicKey &) = delete; + PublicKey &operator=(PublicKey &&) noexcept = delete; + ~PublicKey() = default; + + SecureString as_octet_string() const; + + Status verify_signature(Slice data, Slice signature) const; + + bool operator==(const PublicKey &other) const { + return octet_string_ == other.octet_string_; + } + + bool operator!=(const PublicKey &other) const { + return octet_string_ != other.octet_string_; + } + + private: + SecureString octet_string_; + }; + + class PrivateKey { + public: + static constexpr size_t LENGTH = 32; + + explicit PrivateKey(SecureString octet_string); + + SecureString as_octet_string() const; + + Result<PublicKey> get_public_key() const; + + Result<SecureString> sign(Slice data) const; + + Result<SecureString> as_pem(Slice password) const; + + static Result<PrivateKey> from_pem(Slice pem, Slice password); + + private: + SecureString octet_string_; + }; + + static Result<PrivateKey> generate_private_key(); + + static Result<SecureString> compute_shared_secret(const PublicKey &public_key, const PrivateKey &private_key); + + static Result<SecureString> get_public_key(Slice private_key); +}; + +} // namespace td + +#endif |
