Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions universal/include/userver/crypto/public_key.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ USERVER_NAMESPACE_BEGIN
namespace crypto {

class Certificate;
class PrivateKey;

/// @ingroup userver_universal userver_containers
///
Expand Down Expand Up @@ -49,6 +50,11 @@ class PublicKey {
/// @throw crypto::KeyParseError if failed to load the key.
static PublicKey LoadFromCertificate(const Certificate& cert);

/// Extracts PublicKey from private key.
///
/// @throw crypto::KeyParseError if failed to load the key.
static PublicKey LoadFromPrivateKey(const PrivateKey& private_key);

/// Creates RSA PublicKey from components
///
/// @throw crypto::KeyParseError if failed to load the key.
Expand Down
21 changes: 21 additions & 0 deletions universal/src/crypto/public_key.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <userver/crypto/public_key.hpp>

#include <userver/crypto/certificate.hpp>
#include <userver/crypto/private_key.hpp>

#include <openssl/bn.h>
#include <openssl/pem.h>
Expand Down Expand Up @@ -114,6 +115,26 @@ PublicKey PublicKey::LoadFromCertificate(const Certificate& cert) {
return PublicKey{std::move(pubkey)};
}

PublicKey PublicKey::LoadFromPrivateKey(const PrivateKey& private_key) {
Openssl::Init();

if (!private_key) {
throw KeyParseError(FormatSslError("Failed to load public key from private key: private key is empty"));
}

auto pubkey_bio = MakeBioMemoryBuffer();
if (1 != ::PEM_write_bio_PUBKEY(pubkey_bio.get(), private_key.GetNative())) {
throw KeyParseError(FormatSslError("Failed to write public key from private key"));
}

std::shared_ptr<EVP_PKEY> pubkey(::PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, &NoPasswordCb, nullptr), ::EVP_PKEY_free);
if (!pubkey) {
throw KeyParseError(FormatSslError("Failed to load public key from private key"));
}

return PublicKey{std::move(pubkey)};
}

PublicKey PublicKey::LoadRSAFromComponents(ModulusView modulus, ExponentView exponent) {
auto n = LoadBignumFromBigEnd(modulus.GetUnderlying());
auto e = LoadBignumFromBigEnd(exponent.GetUnderlying());
Expand Down
46 changes: 46 additions & 0 deletions universal/src/crypto/public_key_test.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <gtest/gtest.h>

#include <userver/crypto/exception.hpp>
#include <userver/crypto/private_key.hpp>
#include <userver/crypto/public_key.hpp>
#include <userver/crypto/signers.hpp>
#include <userver/crypto/verifiers.hpp>
Expand Down Expand Up @@ -115,6 +117,22 @@ TEST(Crypto, VerifyRsaFromComponents512) {
namespace {

constexpr crypto::PublicKey::CurveTypeView kType{"P-256"};
constexpr auto kRsa512PrivKey = R"(-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw
33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW
+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB
AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS
3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp
uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE
2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0
GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K
Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY
6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5
fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523
Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP
FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==
-----END RSA PRIVATE KEY-----)";

constexpr auto kEcdsa256v1PrivKey = R"(
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgXBhZSD1+ClYPr7OU
Expand Down Expand Up @@ -146,4 +164,32 @@ TEST(Crypto, EcdsaVerifierFromComponents) {
EXPECT_THROW(verifier.Verify({"Hello World!"}, sign), crypto::VerificationError);
}

TEST(Crypto, PublicKeyFromPrivateKeyRsa) {
const crypto::SignerRs256 signer{kRsa512PrivKey};
const auto sign = signer.Sign({"Hello, World"});

const auto private_key = crypto::PrivateKey::LoadFromString(kRsa512PrivKey);
const auto pub_key = crypto::PublicKey::LoadFromPrivateKey(private_key);
const crypto::VerifierRs256 verifier{pub_key};

EXPECT_NO_THROW(verifier.Verify({"Hello, World"}, sign));
EXPECT_THROW(verifier.Verify({"Hello World!"}, sign), crypto::VerificationError);
}

TEST(Crypto, PublicKeyFromPrivateKeyEc) {
const crypto::SignerEs256 signer{kEcdsa256v1PrivKey};
const auto sign = signer.Sign({"Hello, World"});

const auto private_key = crypto::PrivateKey::LoadFromString(kEcdsa256v1PrivKey);
const auto pub_key = crypto::PublicKey::LoadFromPrivateKey(private_key);
const crypto::VerifierEs256 verifier{pub_key};

EXPECT_NO_THROW(verifier.Verify({"Hello, World"}, sign));
EXPECT_THROW(verifier.Verify({"Hello World!"}, sign), crypto::VerificationError);
}

TEST(Crypto, PublicKeyFromEmptyPrivateKey) {
EXPECT_THROW(crypto::PublicKey::LoadFromPrivateKey(crypto::PrivateKey{}), crypto::KeyParseError);
}

USERVER_NAMESPACE_END