/* * 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 #include #include #include #include #include #include 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(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(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(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{}; 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(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(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(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(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(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(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{}; 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(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(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)); }