295 lines
9.0 KiB
C++
295 lines
9.0 KiB
C++
/*
|
|
* PKey.h
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "flow/AutoCPointer.h"
|
|
#include "flow/Error.h"
|
|
#include "flow/PKey.h"
|
|
#include <openssl/bio.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/objects.h>
|
|
#include <openssl/opensslv.h>
|
|
|
|
namespace {
|
|
|
|
void traceAndThrowDecode(const char* type) {
|
|
auto te = TraceEvent(SevWarnAlways, type);
|
|
te.suppressFor(10);
|
|
if (auto err = ::ERR_get_error()) {
|
|
char buf[256]{
|
|
0,
|
|
};
|
|
::ERR_error_string_n(err, buf, sizeof(buf));
|
|
te.detail("OpenSSLError", static_cast<const char*>(buf));
|
|
}
|
|
throw pkey_decode_error();
|
|
}
|
|
|
|
void traceAndThrowEncode(const char* type) {
|
|
auto te = TraceEvent(SevWarnAlways, type);
|
|
te.suppressFor(10);
|
|
if (auto err = ::ERR_get_error()) {
|
|
char buf[256]{
|
|
0,
|
|
};
|
|
::ERR_error_string_n(err, buf, sizeof(buf));
|
|
te.detail("OpenSSLError", static_cast<const char*>(buf));
|
|
}
|
|
throw pkey_encode_error();
|
|
}
|
|
|
|
void traceAndThrowDsa(const char* type) {
|
|
auto te = TraceEvent(SevWarnAlways, type);
|
|
te.suppressFor(10);
|
|
if (auto err = ::ERR_get_error()) {
|
|
char buf[256]{
|
|
0,
|
|
};
|
|
::ERR_error_string_n(err, buf, sizeof(buf));
|
|
te.detail("OpenSSLError", static_cast<const char*>(buf));
|
|
}
|
|
throw digital_signature_ops_error();
|
|
}
|
|
|
|
inline PKeyAlgorithm getPKeyAlgorithm(const EVP_PKEY* key) noexcept {
|
|
auto id = ::EVP_PKEY_base_id(key);
|
|
if (id == EVP_PKEY_RSA)
|
|
return PKeyAlgorithm::RSA;
|
|
else if (id == EVP_PKEY_EC)
|
|
return PKeyAlgorithm::EC;
|
|
else
|
|
return PKeyAlgorithm::UNSUPPORTED;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
std::string_view pkeyAlgorithmName(PKeyAlgorithm alg) noexcept {
|
|
switch (alg) {
|
|
case PKeyAlgorithm::EC:
|
|
return "EC";
|
|
case PKeyAlgorithm::RSA:
|
|
return "RSA";
|
|
default:
|
|
return "UNSUPPORTED";
|
|
}
|
|
}
|
|
|
|
StringRef doWritePublicKeyPem(Arena& arena, EVP_PKEY* key) {
|
|
ASSERT(key);
|
|
auto mem = AutoCPointer(::BIO_new(::BIO_s_mem()), &::BIO_free);
|
|
if (!mem)
|
|
traceAndThrowEncode("PublicKeyPemWriteInitError");
|
|
if (1 != ::PEM_write_bio_PUBKEY(mem, key))
|
|
traceAndThrowEncode("PublicKeyPemWrite");
|
|
auto bioBuf = std::add_pointer_t<char>{};
|
|
auto const len = ::BIO_get_mem_data(mem, &bioBuf);
|
|
ASSERT_GT(len, 0);
|
|
auto buf = new (arena) uint8_t[len];
|
|
::memcpy(buf, bioBuf, len);
|
|
return StringRef(buf, static_cast<int>(len));
|
|
}
|
|
|
|
StringRef doWritePublicKeyDer(Arena& arena, EVP_PKEY* key) {
|
|
auto len = 0;
|
|
len = ::i2d_PUBKEY(key, nullptr);
|
|
ASSERT_LT(0, len);
|
|
auto buf = new (arena) uint8_t[len];
|
|
auto out = std::add_pointer_t<uint8_t>(buf);
|
|
len = ::i2d_PUBKEY(key, &out);
|
|
return StringRef(buf, len);
|
|
}
|
|
|
|
bool doVerifyStringSignature(StringRef data, StringRef signature, const EVP_MD& digest, EVP_PKEY* key) {
|
|
auto mdctx = AutoCPointer(::EVP_MD_CTX_create(), &::EVP_MD_CTX_free);
|
|
if (!mdctx) {
|
|
traceAndThrowDsa("PKeyVerifyInitFail");
|
|
}
|
|
if (1 != ::EVP_DigestVerifyInit(mdctx, nullptr, &digest, nullptr, key)) {
|
|
traceAndThrowDsa("PKeyVerifyInitFail");
|
|
}
|
|
if (1 != ::EVP_DigestVerifyUpdate(mdctx, data.begin(), data.size())) {
|
|
traceAndThrowDsa("PKeyVerifyUpdateFail");
|
|
}
|
|
if (1 != ::EVP_DigestVerifyFinal(mdctx, signature.begin(), signature.size())) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
PublicKey::PublicKey(PemEncoded, StringRef pem) {
|
|
ASSERT(!pem.empty());
|
|
auto mem = AutoCPointer(::BIO_new_mem_buf(pem.begin(), pem.size()), &::BIO_free);
|
|
if (!mem)
|
|
traceAndThrowDecode("PemMemBioInitError");
|
|
auto key = ::PEM_read_bio_PUBKEY(mem, nullptr, nullptr, nullptr);
|
|
if (!key)
|
|
traceAndThrowDecode("PemReadPublicKeyError");
|
|
ptr = std::shared_ptr<EVP_PKEY>(key, &::EVP_PKEY_free);
|
|
if (algorithm() == PKeyAlgorithm::UNSUPPORTED) {
|
|
TraceEvent(SevWarnAlways, "UnsupportedPKeyAlgorithm")
|
|
.suppressFor(10)
|
|
.detail("Algorithm", ::OBJ_nid2sn(EVP_PKEY_base_id(ptr.get())));
|
|
throw pkey_decode_error();
|
|
}
|
|
}
|
|
|
|
PublicKey::PublicKey(DerEncoded, StringRef der) {
|
|
ASSERT(!der.empty());
|
|
auto data = der.begin();
|
|
auto key = ::d2i_PUBKEY(nullptr, &data, der.size());
|
|
if (!key)
|
|
traceAndThrowDecode("DerReadPublicKeyError");
|
|
ptr = std::shared_ptr<EVP_PKEY>(key, &::EVP_PKEY_free);
|
|
if (algorithm() == PKeyAlgorithm::UNSUPPORTED) {
|
|
TraceEvent(SevWarnAlways, "UnsupportedPKeyAlgorithm")
|
|
.suppressFor(10)
|
|
.detail("Algorithm", ::OBJ_nid2sn(EVP_PKEY_base_id(ptr.get())));
|
|
throw pkey_decode_error();
|
|
}
|
|
}
|
|
|
|
StringRef PublicKey::writePem(Arena& arena) const {
|
|
return doWritePublicKeyPem(arena, nativeHandle());
|
|
}
|
|
|
|
StringRef PublicKey::writeDer(Arena& arena) const {
|
|
return doWritePublicKeyDer(arena, nativeHandle());
|
|
}
|
|
|
|
PKeyAlgorithm PublicKey::algorithm() const {
|
|
auto key = nativeHandle();
|
|
ASSERT(key);
|
|
return getPKeyAlgorithm(key);
|
|
}
|
|
|
|
std::string_view PublicKey::algorithmName() const {
|
|
return pkeyAlgorithmName(this->algorithm());
|
|
}
|
|
|
|
bool PublicKey::verify(StringRef data, StringRef signature, const EVP_MD& digest) const {
|
|
return doVerifyStringSignature(data, signature, digest, nativeHandle());
|
|
}
|
|
|
|
PrivateKey::PrivateKey(PemEncoded, StringRef pem) {
|
|
ASSERT(!pem.empty());
|
|
auto mem = AutoCPointer(::BIO_new_mem_buf(pem.begin(), pem.size()), &::BIO_free);
|
|
if (!mem)
|
|
traceAndThrowDecode("PrivateKeyDecodeInitError");
|
|
auto key = ::PEM_read_bio_PrivateKey(mem, nullptr, nullptr, nullptr);
|
|
if (!key)
|
|
traceAndThrowDecode("PemReadPrivateKeyError");
|
|
ptr = std::shared_ptr<EVP_PKEY>(key, &::EVP_PKEY_free);
|
|
if (algorithm() == PKeyAlgorithm::UNSUPPORTED) {
|
|
TraceEvent(SevWarnAlways, "UnsupportedPKeyAlgorithm")
|
|
.suppressFor(10)
|
|
.detail("Algorithm", ::OBJ_nid2sn(EVP_PKEY_base_id(ptr.get())));
|
|
throw pkey_decode_error();
|
|
}
|
|
}
|
|
|
|
PrivateKey::PrivateKey(DerEncoded, StringRef der) {
|
|
ASSERT(!der.empty());
|
|
auto data = der.begin();
|
|
auto key = ::d2i_AutoPrivateKey(nullptr, &data, der.size());
|
|
if (!key)
|
|
traceAndThrowDecode("DerReadPrivateKeyError");
|
|
ptr = std::shared_ptr<EVP_PKEY>(key, &::EVP_PKEY_free);
|
|
if (algorithm() == PKeyAlgorithm::UNSUPPORTED) {
|
|
TraceEvent(SevWarnAlways, "UnsupportedPKeyAlgorithm")
|
|
.suppressFor(10)
|
|
.detail("Algorithm", ::OBJ_nid2sn(EVP_PKEY_base_id(ptr.get())));
|
|
throw pkey_decode_error();
|
|
}
|
|
}
|
|
|
|
StringRef PrivateKey::writePem(Arena& arena) const {
|
|
ASSERT(ptr);
|
|
auto mem = AutoCPointer(::BIO_new(::BIO_s_mem()), &::BIO_free);
|
|
if (!mem)
|
|
traceAndThrowEncode("PrivateKeyPemWriteInitError");
|
|
if (1 != ::PEM_write_bio_PrivateKey(mem, nativeHandle(), nullptr, nullptr, 0, 0, nullptr))
|
|
traceAndThrowEncode("PrivateKeyDerPemWrite");
|
|
auto bioBuf = std::add_pointer_t<char>{};
|
|
auto const len = ::BIO_get_mem_data(mem, &bioBuf);
|
|
ASSERT_GT(len, 0);
|
|
auto buf = new (arena) uint8_t[len];
|
|
::memcpy(buf, bioBuf, len);
|
|
return StringRef(buf, static_cast<int>(len));
|
|
}
|
|
|
|
StringRef PrivateKey::writeDer(Arena& arena) const {
|
|
ASSERT(ptr);
|
|
auto len = 0;
|
|
len = ::i2d_PrivateKey(nativeHandle(), nullptr);
|
|
ASSERT_LT(0, len);
|
|
auto buf = new (arena) uint8_t[len];
|
|
auto out = std::add_pointer_t<uint8_t>(buf);
|
|
len = ::i2d_PrivateKey(nativeHandle(), &out);
|
|
return StringRef(buf, len);
|
|
}
|
|
|
|
StringRef PrivateKey::writePublicKeyPem(Arena& arena) const {
|
|
return doWritePublicKeyPem(arena, nativeHandle());
|
|
}
|
|
|
|
StringRef PrivateKey::writePublicKeyDer(Arena& arena) const {
|
|
return doWritePublicKeyDer(arena, nativeHandle());
|
|
}
|
|
|
|
PKeyAlgorithm PrivateKey::algorithm() const {
|
|
auto key = nativeHandle();
|
|
ASSERT(key);
|
|
return getPKeyAlgorithm(key);
|
|
}
|
|
|
|
std::string_view PrivateKey::algorithmName() const {
|
|
return pkeyAlgorithmName(this->algorithm());
|
|
}
|
|
|
|
StringRef PrivateKey::sign(Arena& arena, StringRef data, const EVP_MD& digest) const {
|
|
auto key = nativeHandle();
|
|
ASSERT(key);
|
|
auto mdctx = AutoCPointer(::EVP_MD_CTX_create(), &::EVP_MD_CTX_free);
|
|
if (!mdctx)
|
|
traceAndThrowDsa("PKeySignInitError");
|
|
if (1 != ::EVP_DigestSignInit(mdctx, nullptr, &digest, nullptr, nativeHandle()))
|
|
traceAndThrowDsa("PKeySignInitError");
|
|
if (1 != ::EVP_DigestSignUpdate(mdctx, data.begin(), data.size()))
|
|
traceAndThrowDsa("PKeySignUpdateError");
|
|
auto sigLen = size_t{};
|
|
if (1 != ::EVP_DigestSignFinal(mdctx, nullptr, &sigLen)) // assess the length first
|
|
traceAndThrowDsa("PKeySignFinalGetLengthError");
|
|
auto sigBuf = new (arena) uint8_t[sigLen];
|
|
if (1 != ::EVP_DigestSignFinal(mdctx, sigBuf, &sigLen))
|
|
traceAndThrowDsa("SignTokenFinalError");
|
|
return StringRef(sigBuf, sigLen);
|
|
}
|
|
|
|
bool PrivateKey::verify(StringRef data, StringRef signature, const EVP_MD& digest) const {
|
|
return doVerifyStringSignature(data, signature, digest, nativeHandle());
|
|
}
|
|
|
|
PublicKey PrivateKey::toPublic() const {
|
|
auto arena = Arena();
|
|
return PublicKey(DerEncoded{}, writePublicKeyDer(arena));
|
|
}
|