Merge pull request #7088 from sfc-gh-jshim/mtls-test-helpers
mTLS test helpers
This commit is contained in:
commit
0dbbadfd77
|
@ -24,7 +24,9 @@
|
|||
#include "flow/Arena.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/MkCert.h"
|
||||
#include "flow/Platform.h"
|
||||
#include "flow/ScopeExit.h"
|
||||
#include "flow/Trace.h"
|
||||
#include "flow/UnitTest.h"
|
||||
#include <type_traits>
|
||||
|
@ -38,16 +40,6 @@
|
|||
|
||||
namespace {
|
||||
|
||||
template <typename Func>
|
||||
class ExitGuard {
|
||||
std::decay_t<Func> fn;
|
||||
|
||||
public:
|
||||
ExitGuard(Func&& fn) : fn(std::forward<Func>(fn)) {}
|
||||
|
||||
~ExitGuard() { fn(); }
|
||||
};
|
||||
|
||||
[[noreturn]] void traceAndThrow(const char* type) {
|
||||
auto te = TraceEvent(SevWarnAlways, type);
|
||||
te.suppressFor(60);
|
||||
|
@ -56,63 +48,11 @@ public:
|
|||
0,
|
||||
};
|
||||
::ERR_error_string_n(err, buf, sizeof(buf));
|
||||
te.detail("OpenSSLError", buf);
|
||||
te.detail("OpenSSLError", static_cast<const char*>(buf));
|
||||
}
|
||||
throw digital_signature_ops_error();
|
||||
}
|
||||
|
||||
struct KeyPairRef {
|
||||
StringRef privateKey;
|
||||
StringRef publicKey;
|
||||
};
|
||||
|
||||
Standalone<KeyPairRef> generateEcdsaKeyPair() {
|
||||
auto params = std::add_pointer_t<EVP_PKEY>();
|
||||
{
|
||||
auto pctx = ::EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr);
|
||||
ASSERT(pctx);
|
||||
auto ctxGuard = ExitGuard([pctx]() { ::EVP_PKEY_CTX_free(pctx); });
|
||||
ASSERT_LT(0, ::EVP_PKEY_paramgen_init(pctx));
|
||||
ASSERT_LT(0, ::EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1));
|
||||
ASSERT_LT(0, ::EVP_PKEY_paramgen(pctx, ¶ms));
|
||||
ASSERT(params);
|
||||
}
|
||||
auto paramsGuard = ExitGuard([params]() { ::EVP_PKEY_free(params); });
|
||||
// keygen
|
||||
auto kctx = ::EVP_PKEY_CTX_new(params, nullptr);
|
||||
ASSERT(kctx);
|
||||
auto kctxGuard = ExitGuard([kctx]() { ::EVP_PKEY_CTX_free(kctx); });
|
||||
auto key = std::add_pointer_t<EVP_PKEY>();
|
||||
{
|
||||
ASSERT_LT(0, ::EVP_PKEY_keygen_init(kctx));
|
||||
ASSERT_LT(0, ::EVP_PKEY_keygen(kctx, &key));
|
||||
}
|
||||
ASSERT(key);
|
||||
auto keyGuard = ExitGuard([key]() { ::EVP_PKEY_free(key); });
|
||||
|
||||
auto ret = Standalone<KeyPairRef>{};
|
||||
auto& arena = ret.arena();
|
||||
{
|
||||
auto len = 0;
|
||||
len = ::i2d_PrivateKey(key, nullptr);
|
||||
ASSERT_LT(0, len);
|
||||
auto buf = new (arena) uint8_t[len];
|
||||
auto out = std::add_pointer_t<uint8_t>(buf);
|
||||
len = ::i2d_PrivateKey(key, &out);
|
||||
ret.privateKey = StringRef(buf, len);
|
||||
}
|
||||
{
|
||||
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);
|
||||
ret.publicKey = StringRef(buf, len);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Standalone<SignedAuthTokenRef> signToken(AuthTokenRef token, StringRef keyName, StringRef privateKeyDer) {
|
||||
|
@ -127,11 +67,11 @@ Standalone<SignedAuthTokenRef> signToken(AuthTokenRef token, StringRef keyName,
|
|||
if (!key) {
|
||||
traceAndThrow("SignTokenBadKey");
|
||||
}
|
||||
auto keyGuard = ExitGuard([key]() { ::EVP_PKEY_free(key); });
|
||||
auto keyGuard = ScopeExit([key]() { ::EVP_PKEY_free(key); });
|
||||
auto mdctx = ::EVP_MD_CTX_create();
|
||||
if (!mdctx)
|
||||
traceAndThrow("SignTokenInitFail");
|
||||
auto mdctxGuard = ExitGuard([mdctx]() { ::EVP_MD_CTX_free(mdctx); });
|
||||
auto mdctxGuard = ScopeExit([mdctx]() { ::EVP_MD_CTX_free(mdctx); });
|
||||
if (1 != ::EVP_DigestSignInit(mdctx, nullptr, ::EVP_sha256() /*Parameterize?*/, nullptr, key))
|
||||
traceAndThrow("SignTokenInitFail");
|
||||
if (1 != ::EVP_DigestSignUpdate(mdctx, tokenStr.begin(), tokenStr.size()))
|
||||
|
@ -153,11 +93,11 @@ bool verifyToken(SignedAuthTokenRef signedToken, StringRef publicKeyDer) {
|
|||
auto key = ::d2i_PUBKEY(nullptr, &rawPubKeyDer, publicKeyDer.size());
|
||||
if (!key)
|
||||
traceAndThrow("VerifyTokenBadKey");
|
||||
auto keyGuard = ExitGuard([key]() { ::EVP_PKEY_free(key); });
|
||||
auto keyGuard = ScopeExit([key]() { ::EVP_PKEY_free(key); });
|
||||
auto mdctx = ::EVP_MD_CTX_create();
|
||||
if (!mdctx)
|
||||
traceAndThrow("VerifyTokenInitFail");
|
||||
auto mdctxGuard = ExitGuard([mdctx]() { ::EVP_MD_CTX_free(mdctx); });
|
||||
auto mdctxGuard = ScopeExit([mdctx]() { ::EVP_MD_CTX_free(mdctx); });
|
||||
if (1 != ::EVP_DigestVerifyInit(mdctx, nullptr, ::EVP_sha256(), nullptr, key))
|
||||
traceAndThrow("VerifyTokenInitFail");
|
||||
if (1 != ::EVP_DigestVerifyUpdate(mdctx, signedToken.token.begin(), signedToken.token.size()))
|
||||
|
@ -182,7 +122,8 @@ void forceLinkTokenSignTests() {}
|
|||
TEST_CASE("/fdbrpc/TokenSign") {
|
||||
const auto numIters = 100;
|
||||
for (auto i = 0; i < numIters; i++) {
|
||||
auto keyPair = generateEcdsaKeyPair();
|
||||
auto kpArena = Arena();
|
||||
auto keyPair = mkcert::KeyPairRef::make(kpArena);
|
||||
auto token = Standalone<AuthTokenRef>{};
|
||||
auto& arena = token.arena();
|
||||
auto& rng = *deterministicRandom();
|
||||
|
@ -209,15 +150,15 @@ TEST_CASE("/fdbrpc/TokenSign") {
|
|||
token.tenants.push_back(arena, genRandomStringRef());
|
||||
}
|
||||
auto keyName = genRandomStringRef();
|
||||
auto signedToken = signToken(token, keyName, keyPair.privateKey);
|
||||
const auto verifyExpectOk = verifyToken(signedToken, keyPair.publicKey);
|
||||
auto signedToken = signToken(token, keyName, keyPair.privateKeyDer);
|
||||
const auto verifyExpectOk = verifyToken(signedToken, keyPair.publicKeyDer);
|
||||
ASSERT(verifyExpectOk);
|
||||
// try tampering with signed token by adding one more tenant
|
||||
token.tenants.push_back(arena, genRandomStringRef());
|
||||
auto writer = ObjectWriter([&arena](size_t len) { return new (arena) uint8_t[len]; }, IncludeVersion());
|
||||
writer.serialize(token);
|
||||
signedToken.token = writer.toStringRef();
|
||||
const auto verifyExpectFail = verifyToken(signedToken, keyPair.publicKey);
|
||||
const auto verifyExpectFail = verifyToken(signedToken, keyPair.publicKeyDer);
|
||||
ASSERT(!verifyExpectFail);
|
||||
}
|
||||
printf("%d runs OK\n", numIters);
|
||||
|
|
|
@ -48,6 +48,8 @@ set(FLOW_SRCS
|
|||
Knobs.cpp
|
||||
Knobs.h
|
||||
MetricSample.h
|
||||
MkCert.h
|
||||
MkCert.cpp
|
||||
Net2.actor.cpp
|
||||
Net2Packet.cpp
|
||||
Net2Packet.h
|
||||
|
@ -56,6 +58,7 @@ set(FLOW_SRCS
|
|||
Platform.h
|
||||
Profiler.actor.cpp
|
||||
Profiler.h
|
||||
ScopeExit.h
|
||||
SendBufferIterator.h
|
||||
SignalSafeUnwind.cpp
|
||||
SignalSafeUnwind.h
|
||||
|
@ -193,3 +196,6 @@ if(APPLE)
|
|||
target_link_libraries(flow PRIVATE ${IO_KIT} ${CORE_FOUNDATION})
|
||||
target_link_libraries(flow_sampling PRIVATE ${IO_KIT} ${CORE_FOUNDATION})
|
||||
endif()
|
||||
|
||||
add_executable(mkcert MkCertCli.cpp)
|
||||
target_link_libraries(mkcert PUBLIC fmt::fmt flow)
|
||||
|
|
|
@ -0,0 +1,378 @@
|
|||
/*
|
||||
* MkCert.cpp
|
||||
*
|
||||
* 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/Arena.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/MkCert.h"
|
||||
#include "flow/ScopeExit.h"
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/objects.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void traceAndThrow(const char* condition, int line) {
|
||||
auto te = TraceEvent(SevWarnAlways, "MkCertOrKeyError");
|
||||
te.suppressFor(10).detail("Line", line).detail("Condition", condition);
|
||||
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 tls_error();
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
#define OSSL_ASSERT(condition) \
|
||||
do { \
|
||||
if (!(condition)) \
|
||||
traceAndThrow(#condition, __LINE__); \
|
||||
} while (false)
|
||||
|
||||
namespace mkcert {
|
||||
|
||||
// Helper functions working with OpenSSL native types
|
||||
std::shared_ptr<X509> readX509CertPem(StringRef x509CertPem);
|
||||
std::shared_ptr<EVP_PKEY> readPrivateKeyPem(StringRef privateKeyPem);
|
||||
std::shared_ptr<EVP_PKEY> readPrivateKeyPem(StringRef privateKeyPem);
|
||||
std::shared_ptr<EVP_PKEY> makeEllipticCurveKeyPairNative();
|
||||
StringRef writeX509CertPem(Arena& arena, const std::shared_ptr<X509>& nativeCert);
|
||||
StringRef writePrivateKeyPem(Arena& arena, const std::shared_ptr<EVP_PKEY>& nativePrivateKey);
|
||||
|
||||
struct CertAndKeyNative {
|
||||
std::shared_ptr<X509> cert;
|
||||
std::shared_ptr<EVP_PKEY> privateKey;
|
||||
bool valid() const noexcept { return cert && privateKey; }
|
||||
// self-signed cert case
|
||||
bool null() const noexcept { return !cert && !privateKey; }
|
||||
using SelfType = CertAndKeyNative;
|
||||
using PemType = CertAndKeyRef;
|
||||
|
||||
static SelfType fromPem(PemType certAndKey) {
|
||||
auto ret = SelfType{};
|
||||
if (certAndKey.empty())
|
||||
return ret;
|
||||
auto [certPem, keyPem] = certAndKey;
|
||||
// either both set or both unset
|
||||
ASSERT(!certPem.empty() && !keyPem.empty());
|
||||
ret.cert = readX509CertPem(certPem);
|
||||
ret.privateKey = readPrivateKeyPem(keyPem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
PemType toPem(Arena& arena) {
|
||||
auto ret = PemType{};
|
||||
if (null())
|
||||
return ret;
|
||||
ASSERT(valid());
|
||||
ret.certPem = writeX509CertPem(arena, cert);
|
||||
ret.privateKeyPem = writePrivateKeyPem(arena, privateKey);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
CertAndKeyNative makeCertNative(CertSpecRef spec, CertAndKeyNative issuer);
|
||||
|
||||
void printCert(FILE* out, StringRef certPem) {
|
||||
auto x = readX509CertPem(certPem);
|
||||
OSSL_ASSERT(0 < ::X509_print_fp(out, x.get()));
|
||||
}
|
||||
|
||||
void printPrivateKey(FILE* out, StringRef privateKeyPem) {
|
||||
auto key = readPrivateKeyPem(privateKeyPem);
|
||||
auto bio = ::BIO_new_fp(out, BIO_NOCLOSE);
|
||||
OSSL_ASSERT(bio);
|
||||
auto bioGuard = ScopeExit([bio]() { ::BIO_free(bio); });
|
||||
OSSL_ASSERT(0 < ::EVP_PKEY_print_private(bio, key.get(), 0, nullptr));
|
||||
}
|
||||
|
||||
std::shared_ptr<EVP_PKEY> makeEllipticCurveKeyPairNative() {
|
||||
auto params = std::add_pointer_t<EVP_PKEY>();
|
||||
{
|
||||
auto pctx = ::EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr);
|
||||
OSSL_ASSERT(pctx);
|
||||
auto ctxGuard = ScopeExit([pctx]() { ::EVP_PKEY_CTX_free(pctx); });
|
||||
OSSL_ASSERT(0 < ::EVP_PKEY_paramgen_init(pctx));
|
||||
OSSL_ASSERT(0 < ::EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1));
|
||||
OSSL_ASSERT(0 < ::EVP_PKEY_paramgen(pctx, ¶ms));
|
||||
OSSL_ASSERT(params);
|
||||
}
|
||||
auto paramsGuard = ScopeExit([params]() { ::EVP_PKEY_free(params); });
|
||||
// keygen
|
||||
auto kctx = ::EVP_PKEY_CTX_new(params, nullptr);
|
||||
OSSL_ASSERT(kctx);
|
||||
auto kctxGuard = ScopeExit([kctx]() { ::EVP_PKEY_CTX_free(kctx); });
|
||||
auto key = std::add_pointer_t<EVP_PKEY>();
|
||||
OSSL_ASSERT(0 < ::EVP_PKEY_keygen_init(kctx));
|
||||
OSSL_ASSERT(0 < ::EVP_PKEY_keygen(kctx, &key));
|
||||
OSSL_ASSERT(key);
|
||||
return std::shared_ptr<EVP_PKEY>(key, &::EVP_PKEY_free);
|
||||
}
|
||||
|
||||
std::shared_ptr<X509> readX509CertPem(StringRef x509CertPem) {
|
||||
ASSERT(!x509CertPem.empty());
|
||||
auto bio_mem = ::BIO_new_mem_buf(x509CertPem.begin(), x509CertPem.size());
|
||||
OSSL_ASSERT(bio_mem);
|
||||
auto bioGuard = ScopeExit([bio_mem]() { ::BIO_free(bio_mem); });
|
||||
auto ret = ::PEM_read_bio_X509(bio_mem, nullptr, nullptr, nullptr);
|
||||
OSSL_ASSERT(ret);
|
||||
return std::shared_ptr<X509>(ret, &::X509_free);
|
||||
}
|
||||
|
||||
std::shared_ptr<EVP_PKEY> readPrivateKeyPem(StringRef privateKeyPem) {
|
||||
ASSERT(!privateKeyPem.empty());
|
||||
auto bio_mem = ::BIO_new_mem_buf(privateKeyPem.begin(), privateKeyPem.size());
|
||||
OSSL_ASSERT(bio_mem);
|
||||
auto bioGuard = ScopeExit([bio_mem]() { ::BIO_free(bio_mem); });
|
||||
auto ret = ::PEM_read_bio_PrivateKey(bio_mem, nullptr, nullptr, nullptr);
|
||||
OSSL_ASSERT(ret);
|
||||
return std::shared_ptr<EVP_PKEY>(ret, &::EVP_PKEY_free);
|
||||
}
|
||||
|
||||
StringRef writeX509CertPem(Arena& arena, const std::shared_ptr<X509>& nativeCert) {
|
||||
auto mem = ::BIO_new(::BIO_s_mem());
|
||||
OSSL_ASSERT(mem);
|
||||
auto memGuard = ScopeExit([mem]() { ::BIO_free(mem); });
|
||||
OSSL_ASSERT(::PEM_write_bio_X509(mem, nativeCert.get()));
|
||||
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 writePrivateKeyPem(Arena& arena, const std::shared_ptr<EVP_PKEY>& nativePrivateKey) {
|
||||
auto mem = ::BIO_new(::BIO_s_mem());
|
||||
OSSL_ASSERT(mem);
|
||||
auto memGuard = ScopeExit([mem]() { ::BIO_free(mem); });
|
||||
OSSL_ASSERT(::PEM_write_bio_PrivateKey(mem, nativePrivateKey.get(), nullptr, nullptr, 0, 0, nullptr));
|
||||
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));
|
||||
}
|
||||
|
||||
KeyPairRef KeyPairRef::make(Arena& arena) {
|
||||
auto keypair = makeEllipticCurveKeyPairNative();
|
||||
auto ret = KeyPairRef{};
|
||||
{
|
||||
auto len = 0;
|
||||
len = ::i2d_PrivateKey(keypair.get(), nullptr);
|
||||
ASSERT_LT(0, len);
|
||||
auto buf = new (arena) uint8_t[len];
|
||||
auto out = std::add_pointer_t<uint8_t>(buf);
|
||||
len = ::i2d_PrivateKey(keypair.get(), &out);
|
||||
ret.privateKeyDer = StringRef(buf, len);
|
||||
}
|
||||
{
|
||||
auto len = 0;
|
||||
len = ::i2d_PUBKEY(keypair.get(), nullptr);
|
||||
ASSERT_LT(0, len);
|
||||
auto buf = new (arena) uint8_t[len];
|
||||
auto out = std::add_pointer_t<uint8_t>(buf);
|
||||
len = ::i2d_PUBKEY(keypair.get(), &out);
|
||||
ret.publicKeyDer = StringRef(buf, len);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CertAndKeyNative makeCertNative(CertSpecRef spec, CertAndKeyNative issuer) {
|
||||
// issuer key/cert must be both set or both null (self-signed case)
|
||||
ASSERT(issuer.valid() || issuer.null());
|
||||
|
||||
auto const isSelfSigned = issuer.null();
|
||||
auto nativeKeyPair = makeEllipticCurveKeyPairNative();
|
||||
auto newX = ::X509_new();
|
||||
OSSL_ASSERT(newX);
|
||||
auto x509Guard = ScopeExit([&newX]() {
|
||||
if (newX)
|
||||
::X509_free(newX);
|
||||
});
|
||||
auto smartX = std::shared_ptr<X509>(newX, &::X509_free);
|
||||
newX = nullptr;
|
||||
auto x = smartX.get();
|
||||
OSSL_ASSERT(0 < ::X509_set_version(x, 2 /*X509_VERSION_3*/));
|
||||
auto serialPtr = ::X509_get_serialNumber(x);
|
||||
OSSL_ASSERT(serialPtr);
|
||||
OSSL_ASSERT(0 < ::ASN1_INTEGER_set(serialPtr, spec.serialNumber));
|
||||
auto notBefore = ::X509_getm_notBefore(x);
|
||||
OSSL_ASSERT(notBefore);
|
||||
OSSL_ASSERT(::X509_gmtime_adj(notBefore, spec.offsetNotBefore));
|
||||
auto notAfter = ::X509_getm_notAfter(x);
|
||||
OSSL_ASSERT(notAfter);
|
||||
OSSL_ASSERT(::X509_gmtime_adj(notAfter, spec.offsetNotAfter));
|
||||
OSSL_ASSERT(0 < ::X509_set_pubkey(x, nativeKeyPair.get()));
|
||||
auto subjectName = ::X509_get_subject_name(x);
|
||||
OSSL_ASSERT(subjectName);
|
||||
for (const auto& entry : spec.subjectName) {
|
||||
// field names are expected to null-terminate
|
||||
auto fieldName = entry.field.toString();
|
||||
OSSL_ASSERT(0 <
|
||||
::X509_NAME_add_entry_by_txt(
|
||||
subjectName, fieldName.c_str(), MBSTRING_ASC, entry.bytes.begin(), entry.bytes.size(), -1, 0));
|
||||
}
|
||||
auto issuerName = ::X509_get_issuer_name(x);
|
||||
OSSL_ASSERT(issuerName);
|
||||
OSSL_ASSERT(::X509_set_issuer_name(x, (isSelfSigned ? subjectName : ::X509_get_subject_name(issuer.cert.get()))));
|
||||
auto ctx = X509V3_CTX{};
|
||||
X509V3_set_ctx_nodb(&ctx);
|
||||
::X509V3_set_ctx(&ctx, (isSelfSigned ? x : issuer.cert.get()), x, nullptr, nullptr, 0);
|
||||
for (const auto& entry : spec.extensions) {
|
||||
// extension field names and values are expected to null-terminate
|
||||
auto extName = entry.field.toString();
|
||||
auto extValue = entry.bytes.toString();
|
||||
auto extNid = ::OBJ_txt2nid(extName.c_str());
|
||||
if (extNid == NID_undef) {
|
||||
TraceEvent(SevWarnAlways, "MkCertInvalidExtName").suppressFor(10).detail("Name", extName);
|
||||
throw tls_error();
|
||||
}
|
||||
auto ext = ::X509V3_EXT_conf_nid(nullptr, &ctx, extNid, extValue.c_str());
|
||||
OSSL_ASSERT(ext);
|
||||
auto extGuard = ScopeExit([ext]() { ::X509_EXTENSION_free(ext); });
|
||||
OSSL_ASSERT(::X509_add_ext(x, ext, -1));
|
||||
}
|
||||
OSSL_ASSERT(::X509_sign(x, (isSelfSigned ? nativeKeyPair.get() : issuer.privateKey.get()), ::EVP_sha256()));
|
||||
auto ret = CertAndKeyNative{};
|
||||
ret.cert = smartX;
|
||||
ret.privateKey = nativeKeyPair;
|
||||
return ret;
|
||||
}
|
||||
|
||||
CertAndKeyRef CertAndKeyRef::make(Arena& arena, CertSpecRef spec, CertAndKeyRef issuerPem) {
|
||||
auto issuer = CertAndKeyNative::fromPem(issuerPem);
|
||||
auto newCertAndKey = makeCertNative(spec, issuer);
|
||||
return newCertAndKey.toPem(arena);
|
||||
}
|
||||
|
||||
CertSpecRef CertSpecRef::make(Arena& arena, CertKind kind) {
|
||||
auto spec = CertSpecRef{};
|
||||
spec.serialNumber = static_cast<long>(deterministicRandom()->randomInt64(0, 1e10));
|
||||
spec.offsetNotBefore = 0; // now
|
||||
spec.offsetNotAfter = 60 * 60 * 24 * 365; // 1 year from now
|
||||
auto& subject = spec.subjectName;
|
||||
subject.push_back(arena, { "countryName"_sr, "DE"_sr });
|
||||
subject.push_back(arena, { "localityName"_sr, "Berlin"_sr });
|
||||
subject.push_back(arena, { "organizationName"_sr, "FoundationDB"_sr });
|
||||
subject.push_back(arena, { "commonName"_sr, kind.getCommonName("FDB Testing Services"_sr, arena) });
|
||||
auto& ext = spec.extensions;
|
||||
if (kind.isCA()) {
|
||||
ext.push_back(arena, { "basicConstraints"_sr, "critical, CA:TRUE"_sr });
|
||||
ext.push_back(arena, { "keyUsage"_sr, "critical, digitalSignature, keyCertSign, cRLSign"_sr });
|
||||
} else {
|
||||
ext.push_back(arena, { "basicConstraints"_sr, "critical, CA:FALSE"_sr });
|
||||
ext.push_back(arena, { "keyUsage"_sr, "critical, digitalSignature, keyEncipherment"_sr });
|
||||
ext.push_back(arena, { "extendedKeyUsage"_sr, "serverAuth, clientAuth"_sr });
|
||||
}
|
||||
ext.push_back(arena, { "subjectKeyIdentifier"_sr, "hash"_sr });
|
||||
if (!kind.isRootCA())
|
||||
ext.push_back(arena, { "authorityKeyIdentifier"_sr, "keyid, issuer"_sr });
|
||||
return spec;
|
||||
}
|
||||
|
||||
StringRef concatCertChain(Arena& arena, CertChainRef chain) {
|
||||
auto len = 0;
|
||||
for (const auto& entry : chain) {
|
||||
len += entry.certPem.size();
|
||||
}
|
||||
if (len == 0)
|
||||
return StringRef();
|
||||
auto buf = new (arena) uint8_t[len];
|
||||
auto offset = 0;
|
||||
for (auto const& entry : chain) {
|
||||
::memcpy(&buf[offset], entry.certPem.begin(), entry.certPem.size());
|
||||
offset += entry.certPem.size();
|
||||
}
|
||||
UNSTOPPABLE_ASSERT(offset == len);
|
||||
return StringRef(buf, len);
|
||||
}
|
||||
|
||||
CertChainRef makeCertChain(Arena& arena, VectorRef<CertSpecRef> specs, CertAndKeyRef rootAuthority) {
|
||||
ASSERT_GT(specs.size(), 0);
|
||||
// if rootAuthority is empty, use last element in specs to make root CA
|
||||
auto const needRootCA = rootAuthority.empty();
|
||||
if (needRootCA) {
|
||||
int const chainLength = specs.size();
|
||||
auto chain = new (arena) CertAndKeyRef[chainLength];
|
||||
auto caNative = makeCertNative(specs.back(), CertAndKeyNative{} /* empty issuer == self-signed */);
|
||||
chain[chainLength - 1] = caNative.toPem(arena);
|
||||
for (auto i = chainLength - 2; i >= 0; i--) {
|
||||
auto cnkNative = makeCertNative(specs[i], caNative);
|
||||
chain[i] = cnkNative.toPem(arena);
|
||||
caNative = cnkNative;
|
||||
}
|
||||
return CertChainRef(chain, chainLength);
|
||||
} else {
|
||||
int const chainLength = specs.size() + 1; /* account for deep-copied rootAuthority */
|
||||
auto chain = new (arena) CertAndKeyRef[chainLength];
|
||||
auto caNative = CertAndKeyNative::fromPem(rootAuthority);
|
||||
chain[chainLength - 1] = rootAuthority.deepCopy(arena);
|
||||
for (auto i = chainLength - 2; i >= 0; i--) {
|
||||
auto cnkNative = makeCertNative(specs[i], caNative);
|
||||
chain[i] = cnkNative.toPem(arena);
|
||||
caNative = cnkNative;
|
||||
}
|
||||
return CertChainRef(chain, chainLength);
|
||||
}
|
||||
}
|
||||
|
||||
VectorRef<CertSpecRef> makeCertChainSpec(Arena& arena, unsigned length, ESide side) {
|
||||
if (!length)
|
||||
return {};
|
||||
auto specs = new (arena) CertSpecRef[length];
|
||||
auto const isServerSide = side == ESide::Server;
|
||||
for (auto i = 0u; i < length; i++) {
|
||||
auto kind = CertKind{};
|
||||
if (i == 0u)
|
||||
kind = isServerSide ? CertKind(Server{}) : CertKind(Client{});
|
||||
else if (i == length - 1)
|
||||
kind = isServerSide ? CertKind(ServerRootCA{}) : CertKind(ClientRootCA{});
|
||||
else
|
||||
kind = isServerSide ? CertKind(ServerIntermediateCA{ i }) : CertKind(ClientIntermediateCA{ i });
|
||||
specs[i] = CertSpecRef::make(arena, kind);
|
||||
}
|
||||
return VectorRef<CertSpecRef>(specs, length);
|
||||
}
|
||||
|
||||
CertChainRef makeCertChain(Arena& arena, unsigned length, ESide side) {
|
||||
if (!length)
|
||||
return {};
|
||||
// temporary arena for writing up specs
|
||||
auto tmpArena = Arena();
|
||||
auto specs = makeCertChainSpec(tmpArena, length, side);
|
||||
return makeCertChain(arena, specs, {} /*root*/);
|
||||
}
|
||||
|
||||
} // namespace mkcert
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* MkCert.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.
|
||||
*/
|
||||
|
||||
#ifndef MKCERT_H
|
||||
#define MKCERT_H
|
||||
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/Error.h"
|
||||
#include <fmt/format.h>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
namespace mkcert {
|
||||
|
||||
void printCert(FILE* out, StringRef certPem);
|
||||
|
||||
void printPrivateKey(FILE* out, StringRef privateKeyPem);
|
||||
|
||||
struct KeyPairRef {
|
||||
using SelfType = KeyPairRef;
|
||||
|
||||
// Make new Elliptic Curve private-public key pair in DER
|
||||
static SelfType make(Arena& arena);
|
||||
|
||||
StringRef privateKeyDer;
|
||||
StringRef publicKeyDer;
|
||||
};
|
||||
|
||||
struct Asn1EntryRef {
|
||||
// field must match one of ASN.1 object short/long names: e.g. "C", "countryName", "CN", "commonName",
|
||||
// "subjectAltName", ...
|
||||
StringRef field;
|
||||
StringRef bytes;
|
||||
};
|
||||
|
||||
struct ServerRootCA {};
|
||||
struct ServerIntermediateCA {
|
||||
unsigned level;
|
||||
};
|
||||
struct Server {};
|
||||
struct ClientRootCA {};
|
||||
struct ClientIntermediateCA {
|
||||
unsigned level;
|
||||
};
|
||||
struct Client {};
|
||||
|
||||
struct CertKind {
|
||||
|
||||
CertKind() noexcept = default;
|
||||
|
||||
template <class Kind>
|
||||
CertKind(Kind kind) noexcept : value(std::in_place_type<Kind>, kind) {}
|
||||
|
||||
template <class Kind>
|
||||
bool is() const noexcept {
|
||||
return std::holds_alternative<Kind>(value);
|
||||
}
|
||||
|
||||
template <class Kind>
|
||||
Kind const& get() const {
|
||||
return std::get<Kind>(value);
|
||||
}
|
||||
|
||||
bool isServerSide() const noexcept { return is<ServerRootCA>() || is<ServerIntermediateCA>() || is<Server>(); }
|
||||
|
||||
bool isClientSide() const noexcept { return !isServerSide(); }
|
||||
|
||||
bool isRootCA() const noexcept { return is<ServerRootCA>() || is<ClientRootCA>(); }
|
||||
|
||||
bool isIntermediateCA() const noexcept { return is<ServerIntermediateCA>() || is<ClientIntermediateCA>(); }
|
||||
|
||||
bool isLeaf() const noexcept { return is<Server>() || is<Client>(); }
|
||||
|
||||
bool isCA() const noexcept { return !isLeaf(); }
|
||||
|
||||
StringRef getCommonName(StringRef prefix, Arena& arena) const {
|
||||
auto const side = std::string(isClientSide() ? " Client" : " Server");
|
||||
if (isIntermediateCA()) {
|
||||
auto const level = isClientSide() ? get<ClientIntermediateCA>().level : get<ServerIntermediateCA>().level;
|
||||
return prefix.withSuffix(fmt::format("{} Intermediate {}", side, level), arena);
|
||||
} else if (isRootCA()) {
|
||||
return prefix.withSuffix(fmt::format("{} Root", side), arena);
|
||||
} else {
|
||||
return prefix.withSuffix(side, arena);
|
||||
}
|
||||
}
|
||||
|
||||
std::variant<ServerRootCA, ServerIntermediateCA, Server, ClientRootCA, ClientIntermediateCA, Client> value;
|
||||
};
|
||||
|
||||
struct CertSpecRef {
|
||||
using SelfType = CertSpecRef;
|
||||
long serialNumber;
|
||||
// offset in number of seconds relative to now, i.e. cert creation
|
||||
long offsetNotBefore;
|
||||
long offsetNotAfter;
|
||||
VectorRef<Asn1EntryRef> subjectName;
|
||||
// time offset relative to time of cert creation (now)
|
||||
VectorRef<Asn1EntryRef> extensions;
|
||||
// make test-only sample certificate whose fields are inferred from CertKind
|
||||
static SelfType make(Arena& arena, CertKind kind);
|
||||
};
|
||||
|
||||
struct CertAndKeyRef {
|
||||
using SelfType = CertAndKeyRef;
|
||||
StringRef certPem;
|
||||
StringRef privateKeyPem;
|
||||
|
||||
void printCert(FILE* out) {
|
||||
if (!certPem.empty()) {
|
||||
::mkcert::printCert(out, certPem);
|
||||
}
|
||||
}
|
||||
|
||||
void printPrivateKey(FILE* out) {
|
||||
if (!privateKeyPem.empty()) {
|
||||
::mkcert::printPrivateKey(out, privateKeyPem);
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() const noexcept { return certPem.empty() && privateKeyPem.empty(); }
|
||||
|
||||
SelfType deepCopy(Arena& arena) {
|
||||
auto ret = SelfType{};
|
||||
if (!certPem.empty())
|
||||
ret.certPem = StringRef(arena, certPem);
|
||||
if (!privateKeyPem.empty())
|
||||
ret.privateKeyPem = StringRef(arena, privateKeyPem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Empty (default) issuer produces a self-signed certificate
|
||||
static SelfType make(Arena& arena, CertSpecRef spec, CertAndKeyRef issuer);
|
||||
};
|
||||
|
||||
using CertChainRef = VectorRef<CertAndKeyRef>;
|
||||
|
||||
// Concatenate chain of PEMs to one StringRef
|
||||
StringRef concatCertChain(Arena& arena, CertChainRef chain);
|
||||
|
||||
enum class ESide : int { Server, Client };
|
||||
|
||||
// Generate a chain of valid cert specs that have consistent subject/issuer names and
|
||||
// is valid for typical server/client TLS scenario
|
||||
// The 'side' parameter makes a difference in the commonName ("CN") field of the produced specs
|
||||
VectorRef<CertSpecRef> makeCertChainSpec(Arena& arena, unsigned length, ESide side);
|
||||
|
||||
// For empty (default) rootAuthority, the last item in specs is used to generate rootAuthority
|
||||
// Otherwise, rootAuthority is deep-copied to first element of returned chain
|
||||
CertChainRef makeCertChain(Arena& arena, VectorRef<CertSpecRef> specs, CertAndKeyRef rootAuthority);
|
||||
|
||||
// Make stub cert chain of given length inc. root authority
|
||||
// Note: side does not imply anything different other than the choice of common names
|
||||
CertChainRef makeCertChain(Arena& arena, unsigned depth, ESide side);
|
||||
|
||||
} // namespace mkcert
|
||||
|
||||
#endif /*MKCERT_H*/
|
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* MkCertCli.cpp
|
||||
*
|
||||
* 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 <cstdlib>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <fmt/format.h>
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/MkCert.h"
|
||||
#include "flow/network.h"
|
||||
#include "flow/Platform.h"
|
||||
#include "flow/ScopeExit.h"
|
||||
#include "flow/SimpleOpt.h"
|
||||
#include "flow/TLSConfig.actor.h"
|
||||
#include "flow/Trace.h"
|
||||
|
||||
enum EMkCertOpt : int {
|
||||
OPT_HELP,
|
||||
OPT_SERVER_CHAIN_LEN,
|
||||
OPT_CLIENT_CHAIN_LEN,
|
||||
OPT_SERVER_CERT_FILE,
|
||||
OPT_SERVER_KEY_FILE,
|
||||
OPT_SERVER_CA_FILE,
|
||||
OPT_CLIENT_CERT_FILE,
|
||||
OPT_CLIENT_KEY_FILE,
|
||||
OPT_CLIENT_CA_FILE,
|
||||
OPT_EXPIRE_SERVER_CERT,
|
||||
OPT_EXPIRE_CLIENT_CERT,
|
||||
OPT_PRINT_SERVER_CERT,
|
||||
OPT_PRINT_CLIENT_CERT,
|
||||
OPT_PRINT_ARGUMENTS,
|
||||
};
|
||||
|
||||
CSimpleOpt::SOption gOptions[] = { { OPT_HELP, "--help", SO_NONE },
|
||||
{ OPT_HELP, "-h", SO_NONE },
|
||||
{ OPT_SERVER_CHAIN_LEN, "--server-chain-length", SO_REQ_SEP },
|
||||
{ OPT_SERVER_CHAIN_LEN, "-S", SO_REQ_SEP },
|
||||
{ OPT_CLIENT_CHAIN_LEN, "--client-chain-length", SO_REQ_SEP },
|
||||
{ OPT_CLIENT_CHAIN_LEN, "-C", SO_REQ_SEP },
|
||||
{ OPT_SERVER_CERT_FILE, "--server-cert-file", SO_REQ_SEP },
|
||||
{ OPT_SERVER_KEY_FILE, "--server-key-file", SO_REQ_SEP },
|
||||
{ OPT_SERVER_CA_FILE, "--server-ca-file", SO_REQ_SEP },
|
||||
{ OPT_CLIENT_CERT_FILE, "--client-cert-file", SO_REQ_SEP },
|
||||
{ OPT_CLIENT_KEY_FILE, "--client-key-file", SO_REQ_SEP },
|
||||
{ OPT_CLIENT_CA_FILE, "--client-ca-file", SO_REQ_SEP },
|
||||
{ OPT_EXPIRE_SERVER_CERT, "--expire-server-cert", SO_NONE },
|
||||
{ OPT_EXPIRE_CLIENT_CERT, "--expire-client-cert", SO_NONE },
|
||||
{ OPT_PRINT_SERVER_CERT, "--print-server-cert", SO_NONE },
|
||||
{ OPT_PRINT_CLIENT_CERT, "--print-client-cert", SO_NONE },
|
||||
{ OPT_PRINT_ARGUMENTS, "--print-args", SO_NONE },
|
||||
SO_END_OF_OPTIONS };
|
||||
|
||||
template <size_t Len>
|
||||
void printOptionUsage(std::string_view option, const char*(&&optionDescLines)[Len]) {
|
||||
constexpr std::string_view optionIndent{ " " };
|
||||
constexpr std::string_view descIndent{ " " };
|
||||
fmt::print(stdout, "{}{}\n", optionIndent, option);
|
||||
for (auto descLine : optionDescLines)
|
||||
fmt::print(stdout, "{}{}\n", descIndent, descLine);
|
||||
fmt::print("\n");
|
||||
}
|
||||
|
||||
void printUsage(std::string_view binary) {
|
||||
fmt::print(stdout,
|
||||
"mkcert: FDB test certificate chain generator\n\n"
|
||||
"Usage: {} [OPTIONS...]\n\n",
|
||||
binary);
|
||||
printOptionUsage("--server-chain-length LENGTH, -S LENGTH (default: 3)",
|
||||
{ "Length of server certificate chain including root CA certificate." });
|
||||
printOptionUsage("--client-chain-length LENGTH, -C LENGTH (default: 2)",
|
||||
{ "Length of client certificate chain including root CA certificate.",
|
||||
"Use zero-length to test to setup untrusted clients." });
|
||||
printOptionUsage("--server-cert-file PATH (default: 'server_cert.pem')",
|
||||
{ "Output filename for server certificate chain excluding root CA.",
|
||||
"Intended for SERVERS to use as 'tls_certificate_file'.",
|
||||
"Certificates are concatenated in leaf-to-CA order." });
|
||||
printOptionUsage("--server-key-file PATH (default: 'server_key.pem')",
|
||||
{ "Output filename for server private key matching its leaf certificate.",
|
||||
"Intended for SERVERS to use as 'tls_key_file'" });
|
||||
printOptionUsage("--server-ca-file PATH (default: 'server_ca.pem')",
|
||||
{ "Output filename for server's root CA certificate.",
|
||||
"Content same as '--server-cert-file' for '--server-chain-length' == 1.",
|
||||
"Intended for CLIENTS to use as 'tls_ca_file': i.e. cert issuer to trust." });
|
||||
printOptionUsage("--client-cert-file PATH (default: 'client_cert.pem')",
|
||||
{ "Output filename for client certificate chain excluding root CA.",
|
||||
"Intended for CLIENTS to use as 'tls_certificate_file'.",
|
||||
"Certificates are concatenated in leaf-to-CA order." });
|
||||
printOptionUsage("--client-key-file PATH (default: 'client_key.pem')",
|
||||
{ "Output filename for client private key matching its leaf certificate.",
|
||||
"Intended for CLIENTS to use as 'tls_key_file'" });
|
||||
printOptionUsage("--client-ca-file PATH (default: 'client_ca.pem')",
|
||||
{ "Output filename for client's root CA certificate.",
|
||||
"Content same as '--client-cert-file' for '--client-chain-length' == 1.",
|
||||
"Intended for SERVERS to use as 'tls_ca_file': i.e. cert issuer to trust." });
|
||||
printOptionUsage("--expire-server-cert (default: no)",
|
||||
{ "Deliberately expire server's leaf certificate for testing." });
|
||||
printOptionUsage("--expire-client-cert (default: no)",
|
||||
{ "Deliberately expire client's leaf certificate for testing." });
|
||||
printOptionUsage("--print-server-cert (default: no)",
|
||||
{ "Print generated server certificate chain including root in human readable form.",
|
||||
"Printed certificates are in leaf-to-CA order.",
|
||||
"If --print-client-cert is also used, server chain precedes client's." });
|
||||
printOptionUsage("--print-client-cert (default: no)",
|
||||
{ "Print generated client certificate chain including root in human readable form.",
|
||||
"Printed certificates are in leaf-to-CA order.",
|
||||
"If --print-server-cert is also used, server chain precedes client's." });
|
||||
printOptionUsage("--print-args (default: no)", { "Print chain generation arguments." });
|
||||
}
|
||||
|
||||
struct ChainSpec {
|
||||
unsigned length;
|
||||
std::string certFile;
|
||||
std::string keyFile;
|
||||
std::string caFile;
|
||||
mkcert::ESide side;
|
||||
bool expireLeaf;
|
||||
void transformPathToAbs() {
|
||||
certFile = abspath(certFile);
|
||||
keyFile = abspath(keyFile);
|
||||
caFile = abspath(caFile);
|
||||
}
|
||||
void print() {
|
||||
fmt::print(stdout, "{}-side:\n", side == mkcert::ESide::Server ? "Server" : "Client");
|
||||
fmt::print(stdout, " Chain length: {}\n", length);
|
||||
fmt::print(stdout, " Certificate file: {}\n", certFile);
|
||||
fmt::print(stdout, " Key file: {}\n", keyFile);
|
||||
fmt::print(stdout, " CA file: {}\n", caFile);
|
||||
fmt::print(stdout, " Expire cert: {}\n", expireLeaf);
|
||||
}
|
||||
mkcert::CertChainRef makeChain(Arena& arena);
|
||||
};
|
||||
|
||||
mkcert::CertChainRef ChainSpec::makeChain(Arena& arena) {
|
||||
auto checkStream = [](std::ofstream& fs, std::string_view filename) {
|
||||
if (!fs) {
|
||||
throw std::runtime_error(fmt::format("cannot open '{}' for writing", filename));
|
||||
}
|
||||
};
|
||||
auto ofsCert = std::ofstream(certFile, std::ofstream::out | std::ofstream::trunc);
|
||||
checkStream(ofsCert, certFile);
|
||||
auto ofsKey = std::ofstream(keyFile, std::ofstream::out | std::ofstream::trunc);
|
||||
checkStream(ofsKey, keyFile);
|
||||
auto ofsCa = std::ofstream(caFile, std::ofstream::out | std::ofstream::trunc);
|
||||
checkStream(ofsCa, caFile);
|
||||
if (!length)
|
||||
return {};
|
||||
auto specs = mkcert::makeCertChainSpec(arena, length, side);
|
||||
if (expireLeaf) {
|
||||
specs[0].offsetNotBefore = -60l * 60 * 24 * 365;
|
||||
specs[0].offsetNotAfter = -10l;
|
||||
}
|
||||
auto chain = mkcert::makeCertChain(arena, specs, {} /*generate root CA*/);
|
||||
auto ca = chain.back().certPem;
|
||||
ofsCa.write(reinterpret_cast<char const*>(ca.begin()), ca.size());
|
||||
auto chainMinusRoot = chain;
|
||||
if (chainMinusRoot.size() > 1)
|
||||
chainMinusRoot.pop_back();
|
||||
auto cert = mkcert::concatCertChain(arena, chainMinusRoot);
|
||||
ofsCert.write(reinterpret_cast<char const*>(cert.begin()), cert.size());
|
||||
auto key = chain[0].privateKeyPem;
|
||||
ofsKey.write(reinterpret_cast<char const*>(key.begin()), key.size());
|
||||
ofsCert.close();
|
||||
ofsKey.close();
|
||||
ofsCa.close();
|
||||
return chain;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// default chain specs
|
||||
auto serverArgs = ChainSpec{ 3u /*length*/, "server_cert.pem", "server_key.pem",
|
||||
"server_ca.pem", mkcert::ESide::Server, false /* expireLeaf */ };
|
||||
auto clientArgs = ChainSpec{ 2u /*length*/, "client_cert.pem", "client_key.pem",
|
||||
"client_ca.pem", mkcert::ESide::Client, false /* expireLeaf */ };
|
||||
auto printServerCert = false;
|
||||
auto printClientCert = false;
|
||||
auto printArguments = false;
|
||||
auto args = CSimpleOpt(argc, argv, gOptions, SO_O_EXACT | SO_O_HYPHEN_TO_UNDERSCORE);
|
||||
while (args.Next()) {
|
||||
if (auto err = args.LastError()) {
|
||||
switch (err) {
|
||||
case SO_ARG_INVALID_DATA:
|
||||
fmt::print(stderr, "ERROR: invalid argument to option '{}'\n", args.OptionText());
|
||||
return FDB_EXIT_ERROR;
|
||||
case SO_ARG_INVALID:
|
||||
fmt::print(stderr, "ERROR: argument given to no-argument option '{}'\n", args.OptionText());
|
||||
return FDB_EXIT_ERROR;
|
||||
case SO_ARG_MISSING:
|
||||
fmt::print(stderr, "ERROR: argument missing for option '{}'\n", args.OptionText());
|
||||
return FDB_EXIT_ERROR;
|
||||
case SO_OPT_INVALID:
|
||||
fmt::print(stderr, "ERROR: unknown option '{}'\n", args.OptionText());
|
||||
return FDB_EXIT_ERROR;
|
||||
default:
|
||||
fmt::print(stderr, "ERROR: unknown error {} with option '{}'\n", err, args.OptionText());
|
||||
return FDB_EXIT_ERROR;
|
||||
}
|
||||
} else {
|
||||
auto const optId = args.OptionId();
|
||||
switch (optId) {
|
||||
case OPT_HELP:
|
||||
printUsage(argv[0]);
|
||||
return FDB_EXIT_SUCCESS;
|
||||
case OPT_SERVER_CHAIN_LEN:
|
||||
try {
|
||||
serverArgs.length = std::stoul(args.OptionArg());
|
||||
} catch (std::exception const& ex) {
|
||||
fmt::print(stderr, "ERROR: Invalid chain length ({})\n", ex.what());
|
||||
return FDB_EXIT_ERROR;
|
||||
}
|
||||
break;
|
||||
case OPT_CLIENT_CHAIN_LEN:
|
||||
try {
|
||||
clientArgs.length = std::stoul(args.OptionArg());
|
||||
} catch (std::exception const& ex) {
|
||||
fmt::print(stderr, "ERROR: Invalid chain length ({})\n", ex.what());
|
||||
return FDB_EXIT_ERROR;
|
||||
}
|
||||
break;
|
||||
case OPT_SERVER_CERT_FILE:
|
||||
serverArgs.certFile.assign(args.OptionArg());
|
||||
break;
|
||||
case OPT_SERVER_KEY_FILE:
|
||||
serverArgs.keyFile.assign(args.OptionArg());
|
||||
break;
|
||||
case OPT_SERVER_CA_FILE:
|
||||
serverArgs.caFile.assign(args.OptionArg());
|
||||
break;
|
||||
case OPT_CLIENT_CERT_FILE:
|
||||
clientArgs.certFile.assign(args.OptionArg());
|
||||
break;
|
||||
case OPT_CLIENT_KEY_FILE:
|
||||
clientArgs.keyFile.assign(args.OptionArg());
|
||||
break;
|
||||
case OPT_CLIENT_CA_FILE:
|
||||
clientArgs.caFile.assign(args.OptionArg());
|
||||
break;
|
||||
case OPT_EXPIRE_SERVER_CERT:
|
||||
serverArgs.expireLeaf = true;
|
||||
break;
|
||||
case OPT_EXPIRE_CLIENT_CERT:
|
||||
clientArgs.expireLeaf = true;
|
||||
break;
|
||||
case OPT_PRINT_SERVER_CERT:
|
||||
printServerCert = true;
|
||||
break;
|
||||
case OPT_PRINT_CLIENT_CERT:
|
||||
printClientCert = true;
|
||||
break;
|
||||
case OPT_PRINT_ARGUMENTS:
|
||||
printArguments = true;
|
||||
break;
|
||||
default:
|
||||
fmt::print(stderr, "ERROR: Unknown option {}\n", args.OptionText());
|
||||
return FDB_EXIT_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Need to involve flow for the TraceEvent.
|
||||
try {
|
||||
platformInit();
|
||||
Error::init();
|
||||
g_network = newNet2(TLSConfig());
|
||||
openTraceFile(NetworkAddress(), 10 << 20, 10 << 20, ".", "mkcert");
|
||||
auto thread = std::thread([]() {
|
||||
TraceEvent::setNetworkThread();
|
||||
g_network->run();
|
||||
});
|
||||
auto cleanUpGuard = ScopeExit([&thread]() {
|
||||
flushTraceFileVoid();
|
||||
g_network->stop();
|
||||
thread.join();
|
||||
});
|
||||
|
||||
serverArgs.transformPathToAbs();
|
||||
clientArgs.transformPathToAbs();
|
||||
if (printArguments) {
|
||||
serverArgs.print();
|
||||
clientArgs.print();
|
||||
}
|
||||
auto arena = Arena();
|
||||
auto serverChain = serverArgs.makeChain(arena);
|
||||
auto clientChain = clientArgs.makeChain(arena);
|
||||
|
||||
if (printServerCert || printClientCert) {
|
||||
if (printServerCert) {
|
||||
for (auto i = 0; i < serverChain.size(); i++) {
|
||||
mkcert::printCert(stdout, serverChain[i].certPem);
|
||||
}
|
||||
}
|
||||
if (printClientCert) {
|
||||
for (auto i = 0; i < clientChain.size(); i++) {
|
||||
mkcert::printCert(stdout, clientChain[i].certPem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt::print("OK\n");
|
||||
}
|
||||
return FDB_EXIT_SUCCESS;
|
||||
} catch (const Error& e) {
|
||||
fmt::print(stderr, "error: {}\n", e.name());
|
||||
return FDB_EXIT_MAIN_ERROR;
|
||||
} catch (const std::exception& e) {
|
||||
fmt::print(stderr, "exception: {}\n", e.what());
|
||||
return FDB_EXIT_MAIN_EXCEPTION;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* ScopeExit.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.
|
||||
*/
|
||||
|
||||
#ifndef FLOW_SCOPE_EXIT_H
|
||||
#define FLOW_SCOPE_EXIT_H
|
||||
#pragma once
|
||||
|
||||
// Execute lambda as this object goes out of scope
|
||||
template <typename Func>
|
||||
class ScopeExit {
|
||||
std::decay_t<Func> fn;
|
||||
|
||||
public:
|
||||
ScopeExit(Func&& fn) : fn(std::forward<Func>(fn)) {}
|
||||
|
||||
~ScopeExit() { fn(); }
|
||||
};
|
||||
|
||||
#endif /*FLOW_SCOPE_EXIT_H*/
|
Loading…
Reference in New Issue