diff --git a/cpp/proto b/cpp/proto index 3e82fc7d60056e08b041577e332f1ec5029633ee..c581306f8816e5e8b708c9097e402354bb41b84a 160000 --- a/cpp/proto +++ b/cpp/proto @@ -1 +1 @@ -Subproject commit 3e82fc7d60056e08b041577e332f1ec5029633ee +Subproject commit c581306f8816e5e8b708c9097e402354bb41b84a diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index 520ad243c7ff982e01c06d0d7539274c96135545..1e0e2b78089ee9986c27cdbed8308de8d7a7ffaa 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -49,6 +49,7 @@ endif() set(VEREIGNLIB_SRC vereign/core/rand.cc vereign/core/string.cc + vereign/core/time.cc vereign/fs/util.cc vereign/fs/operations.cc vereign/fs/path.cc @@ -76,6 +77,7 @@ set(VEREIGNLIB_SRC vereign/crypto/rsa.cc vereign/crypto/bio.cc vereign/crypto/digest.cc + vereign/crypto/cert.cc vereign/kvstore/lock.cc vereign/kvstore/detail/base_crypto_storage.cc @@ -116,7 +118,7 @@ target_link_libraries(vereignlib PUBLIC gRPC::grpc++_reflection gRPC::grpc++ Boost::filesystem - $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time> + Boost::date_time SQLite::SQLite3 $<$<CXX_COMPILER_ID:MSVC>:ncrypt.lib> $<$<CXX_COMPILER_ID:MSVC>:cryptui.lib> @@ -145,10 +147,12 @@ set(csandbox_sources add_executable(csandbox ${csandbox_sources}) -target_link_libraries(csandbox - PRIVATE vereignlib - $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time> - Boost::filesystem +target_link_libraries(csandbox PRIVATE + # OpenSSL::Crypto + # OpenSSL::SSL + vereignlib + # $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time> + # Boost::filesystem # Boost::file # Boost::thread # vereign diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc index 70382932622480f68ad621fb1f2582e0999d247d..5b7057af6b90f36d495c2110344a6dfa3abd31d6 100644 --- a/cpp/src/csandbox.cc +++ b/cpp/src/csandbox.cc @@ -1,17 +1,22 @@ -#include "vereign/crypto/rand.hh" -#include <vereign/bytes/view.hh> -#include <vereign/bytes/buffer.hh> +#include <vereign/crypto/cert.hh> + +#include <vereign/crypto/rsa.hh> +#include <vereign/crypto/rand.hh> +#include <vereign/crypto/bio.hh> #include <vereign/bytes/view_dump.hh> -#include <vereign/fs/path.hh> #include <vereign/fs/util.hh> +#include <vereign/core/time.hh> +#include <boost/date_time.hpp> + +#include <openssl/x509v3.h> + +#include <chrono> #include <iostream> +#include <fstream> auto main(int argc, char** argv) -> int { argc = 0; argv = nullptr; - auto path = vereign::fs::TempFilePath(vereign::fs::path::Join("tmp", "foo"), "test_db_"); - - std::cout << path << std::endl; return 0; } diff --git a/cpp/src/vereign/core/time.cc b/cpp/src/vereign/core/time.cc new file mode 100644 index 0000000000000000000000000000000000000000..33030082a682d2bc3091602a24d5e39c22da4b96 --- /dev/null +++ b/cpp/src/vereign/core/time.cc @@ -0,0 +1,73 @@ +#include <vereign/core/time.hh> + +namespace vereign::time { + +auto Epoch() -> boost::posix_time::ptime { + static const auto epoch = boost::posix_time::ptime{boost::gregorian::date{1970, 1, 1}}; + + return epoch; +} + +auto PosixTimeToTime(boost::posix_time::ptime t) -> time_t { + return time_t((t - Epoch()).total_seconds()); +} + +auto MakePosixTime( + int year, + int month, + int day, + int hours, + int minutes, + int seconds +) -> boost::posix_time::ptime { + using namespace boost::posix_time; + using namespace boost::gregorian; + + return ptime( + date{ + static_cast<date::year_type>(year), + static_cast<date::month_type>(month), + static_cast<date::day_type>(day) + }, + time_duration(hours, minutes, seconds) + ); +} + +auto MakeTimeUTC( + int year, + int month, + int day, + int hours, + int seconds, + int milliseconds +) -> time_t { + using namespace boost::posix_time; + using namespace boost::gregorian; + + return PosixTimeToTime( + ptime( + date{ + static_cast<date::year_type>(year), + static_cast<date::month_type>(month), + static_cast<date::day_type>(day) + }, + time_duration(hours, seconds, milliseconds) + ) + ); +} + +auto MakeTimeUTCFromString(const std::string& str) -> time_t { + using namespace boost::posix_time; + using namespace boost::gregorian; + + return PosixTimeToTime(boost::posix_time::time_from_string(str)); +} + +auto MakeTimeUTCFromISO(const std::string& str) -> time_t { + using namespace boost::posix_time; + using namespace boost::gregorian; + + return PosixTimeToTime(from_iso_string(str)); +} + +} // vereign::time diff --git a/cpp/src/vereign/core/time.hh b/cpp/src/vereign/core/time.hh new file mode 100644 index 0000000000000000000000000000000000000000..5b7d775606d25a7c2ac264d543bbbdeac833088c --- /dev/null +++ b/cpp/src/vereign/core/time.hh @@ -0,0 +1,72 @@ +#ifndef __VEREIGN_CORE_TIME_HH +#define __VEREIGN_CORE_TIME_HH + +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace vereign::time { + +/** + * Returns the gregorian epoch boost posix time - 1970:01:01. + */ +auto Epoch() -> boost::posix_time::ptime; + +/** + * Converts boost posix time to time_t timestamp. + * + * @param t The source boost posix time. + * @returns timestamp. + */ +auto PosixTimeToTime(boost::posix_time::ptime t) -> time_t; + +/** + * Creates boost posix timetamp. + * + * @param year Year component. + * @param month Month component 1-12. + * @param day Day component 1-31. + * @param hours The hours component 0-23. + * @param minutes The minutes component 0-59. + * @param seconds The minutes component 0-59. + * @returns posix time. + */ +auto MakePosixTime( + int year, + int month, + int day, + int hours, + int minutes, + int seconds +) -> boost::posix_time::ptime; + +/** + * Creates UTC timestamp. + * + * @param year Year component. + * @param month Month component 1-12. + * @param day Day component 1-31. + * @param hours The hours component 0-23. + * @param minutes The minutes component 0-59. + * @param seconds The minutes component 0-59. + * @returns timestamp. + */ +auto MakeTimeUTC(int year, int month, int day, int hours, int seconds, int milliseconds) -> time_t; + +/** + * Creates UTC timestamp from a string. + * + * @param str Time string representation, example: "2002-01-20 23:59:59.000". + * @returns timestamp. + */ +auto MakeTimeUTCFromString(const std::string& str) -> time_t; + +/** + * Creates UTC timestamp from ISO formatted string. + * + * @param str Time string representation, example: "20020120T235959". + * @returns timestamp. + */ +auto MakeTimeUTCFromISO(const std::string& str) -> time_t; + +} // vereign::time + +#endif // __VEREIGN_CORE_TIME_HH diff --git a/cpp/src/vereign/crypto/cert.cc b/cpp/src/vereign/crypto/cert.cc new file mode 100644 index 0000000000000000000000000000000000000000..bc6f84d2d6f74904056f6b5bfe7c4c9d5402d2a4 --- /dev/null +++ b/cpp/src/vereign/crypto/cert.cc @@ -0,0 +1,428 @@ +#include <vereign/crypto/cert.hh> + +#include <vereign/crypto/bio.hh> +#include <vereign/crypto/errors.hh> +#include <vereign/crypto/rand.hh> +#include <vereign/encoding/base64.hh> + +#include <openssl/x509v3.h> +#include <openssl/digest.h> +#include <openssl/pem.h> +#include <sstream> + +namespace { + constexpr const char* certDefaultHashAlg = "SHA256"; + constexpr const int certVersion = 2; +} + +namespace vereign::crypto::cert { + +static auto addCertExtension( + const X509* issuer_cert, + X509* cert, + int nid, + const std::string& value +) -> int { + X509V3_CTX ctx; + + // This sets the 'context' of the extensions. + // No configuration database + X509V3_set_ctx_nodb(&ctx); + + // Issuer and subject certs: both the target since it is self signed, + // no request and no CRL + X509V3_set_ctx(&ctx, const_cast<X509*>(issuer_cert), cert, nullptr, nullptr, 0); + bssl::UniquePtr<X509_EXTENSION> ex{X509V3_EXT_nconf_nid( + nullptr, + &ctx, + nid, + const_cast<std::string&>(value).data() + )}; + if (!ex) { + return 0; + } + + return X509_add_ext(cert, ex.get(), -1); +} + +static void addSubjAltNameExt(X509* cert, const std::string& email, const std::string& url) { + auto gens = bssl::UniquePtr<GENERAL_NAMES>(sk_GENERAL_NAME_new_null()); + if (!gens) { + throw OpenSSLError("creating GENERAL_NAMES stack failed"); + } + + if (!email.empty()) { + auto gen = bssl::UniquePtr<GENERAL_NAME>(GENERAL_NAME_new()); + if (!gen) { + throw OpenSSLError("creating GENERAL_NAME failed"); + } + + auto ia5 = bssl::UniquePtr<ASN1_IA5STRING>(ASN1_IA5STRING_new()); + if (!ia5) { + throw OpenSSLError("creating ASN1_IA5STRING failed"); + } + + auto r = ASN1_STRING_set(ia5.get(), email.data(), -1); + if (r != 1) { + throw OpenSSLError("set certificate alternative name email part failed"); + } + + GENERAL_NAME_set0_value(gen.get(), GEN_EMAIL, ia5.release()); + r = sk_GENERAL_NAME_push(gens.get(), gen.release()); + if (r == 0) { + throw OpenSSLError("pushing email to certificate subject alternative name failed"); + } + } + + if (!url.empty()) { + auto gen = bssl::UniquePtr<GENERAL_NAME>(GENERAL_NAME_new()); + if (!gen) { + throw OpenSSLError("creating GENERAL_NAME failed"); + } + + auto ia5 = bssl::UniquePtr<ASN1_IA5STRING>(ASN1_IA5STRING_new()); + if (!ia5) { + throw OpenSSLError("creating ASN1_IA5STRING failed"); + } + + auto r = ASN1_STRING_set(ia5.get(), url.data(), -1); + if (r != 1) { + throw OpenSSLError("set certificate alternative name URL part failed"); + } + + GENERAL_NAME_set0_value(gen.get(), GEN_DNS, ia5.release()); + r = sk_GENERAL_NAME_push(gens.get(), gen.release()); + if (r == 0) { + throw OpenSSLError("pushing URL to certificate subject alternative name failed"); + } + } + + auto r = X509_add1_ext_i2d(cert, NID_subject_alt_name, gens.get(), 0, 0); + if (r != 1) { + throw OpenSSLError("set certificate alternative name failed"); + } +} + +static void setCertSubject(const CertData& cert_data, X509* cert) { + const CertSubject& subject = cert_data.Subject; + auto subject_name = X509_get_subject_name(cert); + int r = 0; + + if (!subject.CommonName.empty()) { + r = X509_NAME_add_entry_by_NID( + subject_name, + NID_commonName, + MBSTRING_ASC, + (unsigned char*) subject.CommonName.data(), + -1, + -1, + 0 + ); + if (r != 1) { + throw OpenSSLError("set certificate subject Common Name failed"); + } + } + + if (!subject.Country.empty()) { + r = X509_NAME_add_entry_by_NID( + subject_name, + NID_countryName, + MBSTRING_ASC, + (unsigned char*) subject.Country.data(), + -1, + -1, + 0 + ); + if (r != 1) { + throw OpenSSLError("set certificate subject Country Name failed"); + } + } + + if (!subject.Locality.empty()) { + r = X509_NAME_add_entry_by_NID( + subject_name, + NID_localityName, + MBSTRING_ASC, + (unsigned char*) subject.Locality.data(), + -1, + -1, + 0 + ); + if (r != 1) { + throw OpenSSLError("set certificate subject Locality Name failed"); + } + } + + if (!subject.State.empty()) { + r = X509_NAME_add_entry_by_NID( + subject_name, + NID_stateOrProvinceName, + MBSTRING_ASC, + (unsigned char*) subject.State.data(), + -1, + -1, + 0 + ); + if (r != 1) { + throw OpenSSLError("set certificate subject State Name failed"); + } + } + + if (!subject.Organization.empty()) { + r = X509_NAME_add_entry_by_NID( + subject_name, + NID_organizationName, + MBSTRING_ASC, + (unsigned char*) subject.Organization.data(), + -1, + -1, + 0 + ); + if (r != 1) { + throw OpenSSLError("set certificate subject Organization Name failed"); + } + } + + if (!subject.OrganizationUnit.empty()) { + r = X509_NAME_add_entry_by_NID( + subject_name, + NID_organizationalUnitName, + MBSTRING_ASC, + (unsigned char*) subject.OrganizationUnit.data(), + -1, + -1, + 0 + ); + if (r != 1) { + throw OpenSSLError("set certificate subject Organizational Unit Name failed"); + } + } + + + if (!cert_data.Email.empty()) { + r = X509_NAME_add_entry_by_NID( + subject_name, + NID_pkcs9_emailAddress, + MBSTRING_ASC, + (unsigned char*) cert_data.Email.data(), + -1, + -1, + 0 + ); + if (r != 1) { + throw OpenSSLError("set certificate subject Email Address failed"); + } + } +} + +static void addCertExtensions(const CertData& cert_data, const X509* issuer_cert, X509* cert) { + // basic constraints + const std::string basic_constraints = cert_data.IsCA ? "critical,CA:TRUE" : "critical,CA:FALSE"; + auto r = addCertExtension(issuer_cert, cert, NID_basic_constraints, basic_constraints); + if (r != 1) { + throw OpenSSLError("set certificate basic constraints failed"); + } + + // key usage + const std::string key_usage = cert_data.IsCA + ? "critical,digitalSignature,keyCertSign,cRLSign" + : "critical,digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment"; + r = addCertExtension(issuer_cert, cert, NID_key_usage, key_usage); + if (r != 1) { + throw OpenSSLError("set certificate key usage failed"); + } + + // extended key usage + if (!cert_data.IsCA && !cert_data.Email.empty()) { + r = addCertExtension(issuer_cert, cert, NID_ext_key_usage, "emailProtection"); + if (r != 1) { + throw OpenSSLError("set certificate extended key usage failed"); + } + } + + // subject alternative name + if (!cert_data.Email.empty() || !cert_data.Url.empty()) { + addSubjAltNameExt(cert, cert_data.Email, cert_data.Url); + } + + // subject key identifier + r = addCertExtension(issuer_cert, cert, NID_subject_key_identifier, "hash"); + if (r != 1) { + throw OpenSSLError("set certificate key usage failed"); + } + + // authority key identifier + r = addCertExtension(issuer_cert, cert, NID_authority_key_identifier, "keyid,issuer:always"); + if (r != 1) { + throw OpenSSLError("set certificate key usage failed"); + } +} + +static void setCertValidity(const CertData& cert_data, X509* cert) { + using clock = std::chrono::system_clock; + + long not_before_adj = 0; + time_t not_before = cert_data.Validity.NotBefore; + + if (not_before == 0) { + auto now = clock::now(); + not_before = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count(); + not_before_adj = -(not_before % (24 * 60 * 60)); + } + + auto timeret = X509_time_adj(X509_get_notBefore(cert), not_before_adj, ¬_before); + if (timeret == nullptr) { + throw OpenSSLError("adjusting certificate not before validity failed"); + } + + long not_after_adj = 0; + time_t not_after = cert_data.Validity.NotAfter; + + if (not_after == 0) { + not_after = not_before; + + int valid_years = cert_data.Validity.ValidYears; + if (valid_years == 0) { + valid_years = 1; + } + + not_after_adj = not_before_adj + valid_years * 365 * 24 * 60 * 60; + } + + timeret = X509_time_adj(X509_get_notAfter(cert), not_after_adj, ¬_after); + if (timeret == nullptr) { + throw OpenSSLError("adjusting certificate not after validity failed"); + } +} + +static auto createCert( + const CertData& cert_data, + const X509* issuer_cert, + const EVP_PKEY* issuer_pkey, + const EVP_PKEY* pkey +) -> bssl::UniquePtr<X509> { + const EVP_MD* hash_alg = nullptr; + if (cert_data.Algorithms.HashAlg.empty()) { + hash_alg = EVP_get_digestbyname(certDefaultHashAlg); + } else { + hash_alg = EVP_get_digestbyname(cert_data.Algorithms.HashAlg.data()); + } + + if (hash_alg == nullptr) { + throw crypto::OpenSSLError("cannot find hash algorithm"); + } + + auto cert = bssl::UniquePtr<X509>(X509_new()); + + if (issuer_cert == nullptr || issuer_pkey == nullptr) { + issuer_cert = cert.get(); + issuer_pkey = pkey; + } + + // set public key + auto r = X509_set_pubkey(cert.get(), const_cast<EVP_PKEY*>(pkey)); + if (r != 1) { + throw OpenSSLError("set public key to certificate failed"); + } + + // set certificate version + r = X509_set_version(cert.get(), certVersion); + if (r != 1) { + throw OpenSSLError("set version to certificate failed"); + } + + // set serial number + auto serial_number = cert_data.SerialNumber; + if (serial_number == 0) { + serial_number = crypto::RandUint64(); + } + r = ASN1_INTEGER_set_uint64(X509_get_serialNumber(cert.get()), serial_number); + if (r != 1) { + throw OpenSSLError("set certificate serial number"); + } + + // set subject + setCertSubject(cert_data, cert.get()); + + // set issuer name + auto issuer_name = X509_get_subject_name(const_cast<X509*>(issuer_cert)); + r = X509_set_issuer_name(cert.get(), issuer_name); + if (r != 1) { + throw OpenSSLError("set certificate issuer name failed"); + } + + // set validity + setCertValidity(cert_data, cert.get()); + + // add extensions + addCertExtensions(cert_data, issuer_cert, cert.get()); + + r = X509_sign(cert.get(), const_cast<EVP_PKEY*>(issuer_pkey), hash_alg); + if (r == 0) { + throw OpenSSLError("certificate sign failed"); + } + + return cert; +} + +auto CreateCert( + const CertData& cert_data, + const X509* issuer_cert, + const EVP_PKEY* issuer_pkey, + const EVP_PKEY* pkey +) -> bssl::UniquePtr<X509> { + + return createCert(cert_data, issuer_cert, issuer_pkey, pkey); +} + +auto CreateSelfSignedCert( + const CertData& cert_data, + const EVP_PKEY* pkey +) -> bssl::UniquePtr<X509> { + + return createCert(cert_data, nullptr, nullptr, pkey); +} + +auto ExportCertToPEM(const X509* cert) -> bssl::UniquePtr<BIO> { + bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem())); + if (!mem) { + throw OpenSSLError("creating memory buffer failed"); + } + + auto r = PEM_write_bio_X509(mem.get(), const_cast<X509*>(cert)); + if (r != 1) { + throw OpenSSLError("exporting certificate to PEM failed"); + } + + return mem; +} + +auto ImportCertFromPEM(bytes::View pem) -> bssl::UniquePtr<X509> { + bssl::UniquePtr<BIO> mem(BIO_new_mem_buf(pem.Data(), pem.Size())); + if (mem == nullptr) { + throw OpenSSLError("creating memory buffer failed"); + } + + auto cert = bssl::UniquePtr<X509>(PEM_read_bio_X509(mem.get(), nullptr, nullptr, nullptr)); + if (cert == nullptr) { + throw OpenSSLError("importing certificate from PEM failed"); + } + + return cert; +} + +void PrintCert(std::ostream& os, const X509* cert) { + bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem())); + if (!mem) { + throw OpenSSLError("creating memory buffer failed"); + } + + auto r = X509_print(mem.get(), const_cast<X509*>(cert)); + if (r != 1) { + throw OpenSSLError("printing certificate failed"); + } + + os << bio::View(mem.get()).String(); +} + +} // vereign::crypto::cert diff --git a/cpp/src/vereign/crypto/cert.hh b/cpp/src/vereign/crypto/cert.hh new file mode 100644 index 0000000000000000000000000000000000000000..6a5ed4e010a68dce886223936afbb6f537ded563 --- /dev/null +++ b/cpp/src/vereign/crypto/cert.hh @@ -0,0 +1,170 @@ +#ifndef __VEREIGN_CRYPTO_CERT_HH +#define __VEREIGN_CRYPTO_CERT_HH + +#include <vereign/bytes/buffer.hh> + +#include <openssl/base.h> +#include <openssl/x509.h> +#include <chrono> + +namespace vereign::crypto::cert { + +/** + * Certificate algorithms configuration used for creating certificates. + * + * Used in CertData struct. + */ +struct CertAlgorithms { + /** + * The hash algorithm, if it is empty the default SHA256 will be used. + */ + std::string HashAlg; + + // FIXME: SignAlg is unused + std::string SignAlg; + // FIXME: KeyLength is unused + int KeyLength; +}; + +/** + * Certificate subject configuration used for creating certificates. + * + * All fields are optional for leaf, recommended for CA. + * + * Used in CertData struct. + */ +struct CertSubject { + std::string CommonName; + std::string Country; + std::string Locality; + std::string State; + std::string Organization; + std::string OrganizationUnit; +}; + +/** + * Certificate validity configuration used for creating certificates. + * + * Used in CertData struct. + */ +struct CertValidity { + /** + * If 0, the default is today at 00:00:00. + */ + time_t NotBefore; + + /** + * If 0, the default is NotBefore + ValidYears at 23:59:59. + */ + time_t NotAfter; + + /** + * If 0, the default is 1. + */ + int ValidYears; +}; + +/** + * Certificate configuration used for creating certificates. + * + * @see CreateCert + * @see CreateSelfSignedCert + */ +struct CertData { + CertAlgorithms Algorithms; + CertSubject Subject; + CertValidity Validity; + + /** + * If 0, the default is the current milliseconds timestamp - the number of milliseconds since epoch. + */ + uint64_t SerialNumber; + + /** + * Added to DN and Subject Alternative Name extension. + * Optional for CA. Mandatory for leaf certificate, used for email protection. + */ + std::string Email; + + /** + * Optional, recommended for CA, added to Subject Alternative Name extension. + */ + std::string Url; + + /** + * Denotes if the created certificate is CA or leaf certificate. The default is false. + */ + bool IsCA; +}; + +/** + * Creates and signs a certificate. + * + * @param cert_data Certificate configuration. + * @param issuer_cert The issuer certificate. + * @param issuer_pkey The issuer private key. + * @param pkey The private key for the newly created certificate. + * @returns new certificate signed by the provided issuer. + * + * @throws crypto::OpenSSLError on failure. + */ +auto CreateCert( + const CertData& cert_data, + const X509* issuer_cert, + const EVP_PKEY* issuer_pkey, + const EVP_PKEY* pkey +) -> bssl::UniquePtr<X509>; + +/** + * Creates self signed certificate. + * + * @param cert_data Certificate configuration. + * @param pkey The private key for the newly created certificate. + * @returns the new certificate signed by its own private key. + * + * @throws crypto::OpenSSLError on failure. + */ +auto CreateSelfSignedCert( + const CertData& cert_data, + const EVP_PKEY* pkey +) -> bssl::UniquePtr<X509>; + +/** + * Exports certificate to PEM format. + * + * @param cert The certificate to export. + * @returns a memory BIO with the exported certificate. + */ +auto ExportCertToPEM(const X509* cert) -> bssl::UniquePtr<BIO>; + +/** + * Exports certificate to PEM format into string. + * + * @param cert The certificate to export. + * @returns a string with the exported certificate. + * + * @throws crypto::OpenSSLError on failure. + */ +auto ExportCertToPEMString(const X509* cert) -> std::string; + +/** + * Imports certificate from PEM format. + * + * @param pem PEM encoded certificate. + * @returns imported X509 certificate. + * + * @throws crypto::OpenSSLError on failure. + */ +auto ImportCertFromPEM(bytes::View pem) -> bssl::UniquePtr<X509>; + +/** + * Print textual representation of the certificate into output stream. + * + * @param os The stream where to write the textual representation of the certificate. + * @param cert The certificate. + */ +void PrintCert(std::ostream& os, const X509* cert); + +} // vereign::crypto::cert + +#endif // __VEREIGN_CRYPTO_CERT_HH diff --git a/cpp/src/vereign/crypto/errors.hh b/cpp/src/vereign/crypto/errors.hh index 949eb248cd8a0caf3014701a8e962c47f866f1ad..825af7e98baf03a468ace2ced84cea76960b5c4f 100644 --- a/cpp/src/vereign/crypto/errors.hh +++ b/cpp/src/vereign/crypto/errors.hh @@ -23,9 +23,19 @@ public: class OpenSSLError : public Error { public: OpenSSLError(const std::string& what) - : Error(what + ": " + ERR_reason_error_string(ERR_get_error())) + : Error(getOpenSSLErrorString(what)) { } + +private: + static auto getOpenSSLErrorString(const std::string& what) -> std::string { + auto error = ERR_get_error(); + if (error == 0) { + return what + ": no openssl error in the error queue"; + } + + return what + ": " + ERR_reason_error_string(error); + } }; } // vereign::crypto diff --git a/cpp/src/vereign/crypto/rand.cc b/cpp/src/vereign/crypto/rand.cc index 2acc168ed6d9df6905c3ad25e7053c041e29a715..f61274887d5a826a22e0db178c86564ae2adcad8 100644 --- a/cpp/src/vereign/crypto/rand.cc +++ b/cpp/src/vereign/crypto/rand.cc @@ -23,4 +23,15 @@ auto Rand(std::size_t size) -> bytes::Buffer { return buf; } +auto RandUint64() -> uint64_t { + uint64_t x = 0; + int result = RAND_bytes((uint8_t*) &x, sizeof(x)); + if (result == 0) { + ERR_clear_error(); + throw Error("crypto rand failed"); + } + + return x; +} + } // vereign::crypto diff --git a/cpp/src/vereign/crypto/rand.hh b/cpp/src/vereign/crypto/rand.hh index 4c3979900f1464d2e54c9957f689f503af3e7e2f..e377ac88ce42544b57f337ef82f8ba7c9b24f71c 100644 --- a/cpp/src/vereign/crypto/rand.hh +++ b/cpp/src/vereign/crypto/rand.hh @@ -43,6 +43,15 @@ void Rand(bytes::Buffer& buf, std::size_t size); */ auto Rand(std::size_t size) -> bytes::Buffer; +/** + * Generates random uint64_t. + * + * @returns random unsigned 64 bit integer. + * + * @throws crypto::Error on failure. + */ +auto RandUint64() -> uint64_t; + } // vereign::crypto #endif // __VEREIGN_CRYPTO_RAND_HH diff --git a/cpp/src/vereign/crypto/rsa.cc b/cpp/src/vereign/crypto/rsa.cc index e9a519f6b5887486f37819759e65d33108264602..2c89dfcb8078be5b4f232a33eb9c23781690b8cc 100644 --- a/cpp/src/vereign/crypto/rsa.cc +++ b/cpp/src/vereign/crypto/rsa.cc @@ -3,6 +3,7 @@ #include <vereign/bytes/view.hh> #include <vereign/crypto/errors.hh> #include <vereign/bytes/buffer.hh> +#include <vereign/crypto/bio.hh> #include <openssl/base.h> #include <openssl/bn.h> @@ -150,6 +151,12 @@ auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO> { return mem; } +auto ExportPrivateKeyToPEMString(EVP_PKEY* key) -> std::string { + auto key_bio = ExportPublicKeyToPEM(key); + + return std::string{crypto::bio::View(key_bio.get()).String()}; +} + auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY> { bssl::UniquePtr<BIO> mem(BIO_new_mem_buf(pem.Data(), pem.Size())); if (mem == nullptr) { @@ -158,7 +165,7 @@ auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY> { auto key_ptr = PEM_read_bio_PrivateKey(mem.get(), nullptr, nullptr, nullptr); if (key_ptr == nullptr) { - throw OpenSSLError("importing public key from PEM failed"); + throw OpenSSLError("importing private key from PEM failed"); } return bssl::UniquePtr<EVP_PKEY>(key_ptr); diff --git a/cpp/src/vereign/crypto/rsa.hh b/cpp/src/vereign/crypto/rsa.hh index 4d8e18fc38701d7a921dc2ea42a2781030ee32a6..03a6c136503f3b0bfdda1b1df80d3a8a640b80f1 100644 --- a/cpp/src/vereign/crypto/rsa.hh +++ b/cpp/src/vereign/crypto/rsa.hh @@ -125,6 +125,23 @@ auto ImportPublicKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>; */ auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>; +/** + * Export private key from PEM format. + * + * @code + * auto key = crypto::rsa::GenerateKey(2048); + * + * auto pem = crypto::rsa::ExportPrivateKeyToPEMString(key.get()); + * std::cout << pem << std::endl; + * @endcode + * + * @param key The key to export. + * @returns the exported key in PEM format. + * + * @throws crypto::OpenSSLError on failure. + */ +auto ExportPrivateKeyToPEMString(EVP_PKEY* key) -> std::string; + /** * Import private key from PEM format. * diff --git a/cpp/src/vereign/encoding/base64.cc b/cpp/src/vereign/encoding/base64.cc index 47ef85a53f69d204c474b6ad862a1cc0942ebbfa..88e1e539f83ac5c8cb960a500a7b605fde093fcc 100644 --- a/cpp/src/vereign/encoding/base64.cc +++ b/cpp/src/vereign/encoding/base64.cc @@ -14,6 +14,19 @@ void Encode(bytes::View src, bytes::Buffer& encoded) { encoded.IncSize(written); } +auto EncodeToString(bytes::View src) -> std::string { + if (src.Size() == 0) { + return ""; + } + + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(src.Size())); + auto written = boost::beast::detail::base64::encode(encoded.data(), src.Data(), src.Size()); + encoded.resize(written); + + return encoded; +} + void Decode(bytes::View src, bytes::Buffer& decoded) { if (src.Size() == 0) { return ; diff --git a/cpp/src/vereign/encoding/base64.hh b/cpp/src/vereign/encoding/base64.hh index a69a559403f9126045cdb69e89bbf084a519fd08..d3ee3ce4b13a22628dca0c1fe76653e762c3aacd 100644 --- a/cpp/src/vereign/encoding/base64.hh +++ b/cpp/src/vereign/encoding/base64.hh @@ -25,6 +25,24 @@ namespace vereign::encoding::base64 { */ void Encode(bytes::View src, bytes::Buffer& encoded); +/** + * Encodes source bytes into base64 encoding. + * + * No new lines are inserted. + * + * Example: + * @code + * std::string s{"foob"}; + * auto encoded = encoding::base64::Encode(bytes::View(s)); + * + * assert(encoded == "Zm9vYg==") + * @endcode + * + * @param src The source bytes that will be encoded. + * @returns the encoded string. + */ +auto EncodeToString(bytes::View src) -> std::string; + /** * Decodes base64 encoded bytes. * diff --git a/cpp/src/vereign/fs/errors.hh b/cpp/src/vereign/fs/errors.hh index 035dc67edc34f39c03bbd90bbc649896af48cc3b..ef72c8c00655fd544e4cca926ca1493f94161d73 100644 --- a/cpp/src/vereign/fs/errors.hh +++ b/cpp/src/vereign/fs/errors.hh @@ -1,25 +1,32 @@ #ifndef __VEREIGN_FS_ERRORS_HH #define __VEREIGN_FS_ERRORS_HH -#include <exception> +#include <string> +#include <stdexcept> namespace vereign::fs { -class Error : public std::exception { - auto what() const noexcept -> const char* override { - return "filesystem error"; +class Error : public std::runtime_error { +public: + Error(const std::string& what) + : std::runtime_error{what} + { } }; -class HomeNotFoundError : public std::exception { - auto what() const noexcept -> const char* override { - return "cannot find user's home dir"; +class HomeNotFoundError : public Error { +public: + HomeNotFoundError() + : Error{"cannot find user's home dir"} + { } }; -class TempDirNotFoundError : public std::exception { - auto what() const noexcept -> const char* override { - return "cannot find user's temp dir"; +class TempDirNotFoundError : public Error { +public: + TempDirNotFoundError() + : Error{"cannot find user's temp dir"} + { } }; diff --git a/cpp/src/vereign/fs/util.cc b/cpp/src/vereign/fs/util.cc index 2e39a022f9ecd8d38388467b85beaedb955856d6..8d60e3e827c1228e2f5db2a00444ebab66674db2 100644 --- a/cpp/src/vereign/fs/util.cc +++ b/cpp/src/vereign/fs/util.cc @@ -7,6 +7,10 @@ #include <vereign/core/rand.hh> #include <vereign/core/string.hh> #include <boost/filesystem/operations.hpp> +#include <boost/filesystem/fstream.hpp> + +// FIXME: remove +#include <iostream> #ifdef _WIN32 # include <shlobj_core.h> @@ -110,4 +114,40 @@ auto HomePath() -> std::string { #endif } +auto ReadFile(std::string_view path) -> bytes::Buffer { + boost::filesystem::ifstream file{ + detail::StringToPath(path), + boost::filesystem::fstream::binary | boost::filesystem::fstream::ate, + }; + + if (!file.is_open()) { + throw fs::Error("open file for reading failed"); + } + + auto size = file.tellg(); + bytes::Buffer result{static_cast<size_t>(size)}; + file.seekg(0); + + file.read((char*) result.end(), size); + result.IncSize(file.gcount()); + + file.close(); + + return result; +} + +void WriteFile(std::string_view path, bytes::View data) { + boost::filesystem::ofstream file{ + detail::StringToPath(path), + boost::filesystem::fstream::binary | boost::filesystem::fstream::trunc, + }; + + if (!file.is_open()) { + throw fs::Error("open file for writing failed"); + } + + file.write(data.CharData(), data.Size()); + file.close(); +} + } // namespace vereign::fs diff --git a/cpp/src/vereign/fs/util.hh b/cpp/src/vereign/fs/util.hh index c4e9d3dbb03b08f32a63de99fd6a1da389cc8f35..d665902476cd8f2760a7729fe3649027463c13cc 100644 --- a/cpp/src/vereign/fs/util.hh +++ b/cpp/src/vereign/fs/util.hh @@ -1,6 +1,7 @@ #ifndef __VEREIGN_FS_UTIL_HH #define __VEREIGN_FS_UTIL_HH +#include <vereign/bytes/buffer.hh> #include <boost/filesystem/path.hpp> #include <string> #include <string_view> @@ -108,6 +109,9 @@ auto TempDir(std::string_view prefix) -> std::string; */ auto HomePath() -> std::string; +auto ReadFile(std::string_view path) -> bytes::Buffer; +void WriteFile(std::string_view path, bytes::View data); + } // namespace vereign::fs #endif // __VEREIGN_FS_UTIL_HH diff --git a/cpp/src/vereign/grpc/server.cc b/cpp/src/vereign/grpc/server.cc index f0fae25a176de979bd9274693687a1d1ef6b272f..a20883987ce7586e61bacb63eda286956bbc8e3a 100644 --- a/cpp/src/vereign/grpc/server.cc +++ b/cpp/src/vereign/grpc/server.cc @@ -60,7 +60,7 @@ public: kvstorage_ = std::make_unique<kvstore::SqliteStorage>(storage_path); crypto_storage_ = std::make_unique<kvstore::CryptoStorage>(*kvstorage_); - identity_provider_ = std::make_unique<identity::Provider>(*crypto_storage_); + identity_provider_ = std::make_unique<identity::Provider>(*client_session_, *crypto_storage_); // FIXME: Verify the remote server's certificate // ssl_context.set_verify_mode(ssl::verify_peer); diff --git a/cpp/src/vereign/identity/errors.hh b/cpp/src/vereign/identity/errors.hh index 7140afec338eeef55150bc988beb260de2b901ac..ffc59e9f9564d081c8c3467747cab9055e5a8598 100644 --- a/cpp/src/vereign/identity/errors.hh +++ b/cpp/src/vereign/identity/errors.hh @@ -13,6 +13,25 @@ public: } }; +class ServerSignCertificateError : public Error { +public: + std::string Code; + std::string Status; + std::string Error; + + ServerSignCertificateError( + std::string code, + std::string status, + std::string error + ) + : identity::Error(error), + Code{std::move(code)}, + Status{std::move(status)}, + Error{std::move(error)} + { + } +}; + } // namespace vereign::identity #endif // __VEREIGN_IDENTITY_ERRORS_HH diff --git a/cpp/src/vereign/identity/provider.cc b/cpp/src/vereign/identity/provider.cc index e69494ad66845c3fdd56216b20559ffffb887942..f50c045aeab7bfbcf63dbf647b13e547d4a0faa5 100644 --- a/cpp/src/vereign/identity/provider.cc +++ b/cpp/src/vereign/identity/provider.cc @@ -1,18 +1,54 @@ #include <vereign/identity/provider.hh> +#include <vereign/identity/errors.hh> + #include <vereign/crypto/digest.hh> #include <vereign/crypto/bio.hh> #include <vereign/crypto/rsa.hh> +#include <vereign/crypto/cert.hh> #include <vereign/encoding/base64.hh> +#include <vereign/client_library/sign_types.pb.h> + namespace { - constexpr int rsaKeySizeBits = 2048; + +constexpr int rsaKeySizeBits = 2048; +constexpr const auto profileCertCountry = std::string_view{"CH"}; +constexpr const auto profileCertState = std::string_view{"Zug"}; +constexpr const auto profileCertLocality = std::string_view{"Zug"}; +constexpr const auto profileCertOrganization = std::string_view{"Vereign AG"}; +constexpr const auto profileCertOrganizationUnit = std::string_view{"Business Dep"}; +constexpr const int profileCertValidityYears = 5; + +const auto signCertificatePath = std::string{"/sign/signCertificate"}; + } namespace vereign::identity { -Provider::Provider(kvstore::CryptoStorage& storage) - : storage_{storage} +static auto makeProfileCertificate( + bytes::View key_pem, + const client_library::SignCertificateFormResponsePayload& msg +) -> std::unique_ptr<ProfileCertificate> { + + auto profile_cert = std::make_unique<ProfileCertificate>(); + + profile_cert->PrivateKeyPEM = bytes::Buffer{key_pem}; + encoding::base64::Decode(bytes::View(msg.signedcertificate()), profile_cert->CertificatePEM); + profile_cert->CertificateUUID = msg.certificateuuid(); + + for (auto& cert : msg.chain()) { + bytes::Buffer buf; + encoding::base64::Decode(bytes::View(cert), buf); + profile_cert->Chain.push_back(std::move(buf)); + } + + return profile_cert; +} + +Provider::Provider(restapi::ClientSession& client_session, kvstore::CryptoStorage& storage) + : client_session_{client_session}, + storage_{storage} {} Provider::~Provider() = default; @@ -77,4 +113,129 @@ auto Provider::GetDeviceHash() -> std::string { return std::string(encoded.View().String()); } +auto Provider::getProfileCertificateFromStorage( + const std::string& profile_uuid +) -> std::unique_ptr<ProfileCertificate> { + + std::lock_guard<std::mutex> l{mu_}; + + auto key_pem = bytes::Buffer{}; + auto cert_binary = bytes::Buffer{}; + + storage_.GetBytes("profile-key-" + profile_uuid, key_pem); + storage_.GetBytes("profile-cert-" + profile_uuid, cert_binary); + + if (key_pem.Size() == 0 || cert_binary.Size() == 0) { + return nullptr; + } + + auto cert_msg = client_library::SignCertificateFormResponsePayload{}; + auto result = cert_msg.ParseFromArray(cert_binary.View().Data(), cert_binary.View().Size()); + if (!result) { + return nullptr; + } + + return makeProfileCertificate(key_pem.View(), cert_msg); +} + +auto Provider::GetProfileCertificate( + const std::string& profile_uuid +) -> std::unique_ptr<ProfileCertificate> { + auto profile_cert = getProfileCertificateFromStorage(profile_uuid); + if (profile_cert) { + return profile_cert; + } + + auto key = crypto::rsa::GenerateKey(rsaKeySizeBits); + + crypto::cert::CertData cert_data{}; + cert_data.Subject.CommonName = profile_uuid + "-userdevice"; + cert_data.Subject.Country = profileCertCountry; + cert_data.Subject.State = profileCertState; + cert_data.Subject.Locality = profileCertLocality; + cert_data.Subject.Organization = profileCertOrganization; + cert_data.Subject.OrganizationUnit = profileCertOrganizationUnit; + + // cert_data.Email = "ca@vereign.com"; // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection + // cert_data.Url = "www.vereign.com"; // optional url, recommended for CA, added to Subject Alternative Name extension + + cert_data.Validity.ValidYears = profileCertValidityYears; + cert_data.IsCA = true; + + auto cert = crypto::cert::CreateSelfSignedCert(cert_data, key.get()); + auto cert_pem_bio = crypto::cert::ExportCertToPEM(cert.get()); + auto cert_pem = encoding::base64::EncodeToString(crypto::bio::View(cert_pem_bio.get())); + + // sign the new profile certificate + auto req = client_library::SignCertificateForm{}; + req.set_passportuuid(profile_uuid); + req.set_certificate(cert_pem); + auto resp = client_library::SignCertificateFormResponse{}; + + auto result = client_session_.Post(signCertificatePath, &req, &resp); + result.wait(); + + if (resp.code() != "200") { + throw ServerSignCertificateError(resp.code(), resp.status(), resp.error()); + } + + // serialize cert data + auto payload_size = resp.data().ByteSizeLong(); + auto cert_binary = bytes::Buffer{payload_size}; + resp.data().SerializeToArray(cert_binary.end(), payload_size); + cert_binary.IncSize(payload_size); + + auto key_bio = crypto::rsa::ExportPrivateKeyToPEM(key.get()); + + { + std::lock_guard<std::mutex> l{mu_}; + + storage_.PutBytes("profile-key-" + profile_uuid, crypto::bio::View(key_bio.get())); + storage_.PutBytes("profile-cert-" + profile_uuid, cert_binary.View()); + } + + return makeProfileCertificate(crypto::bio::View(key_bio.get()), resp.data()); +} + +auto Provider::GetProfileOneTimeCertificate( + const std::string& profile_uuid, + const std::string& email +) -> std::unique_ptr<ProfileCertificate> { + auto profile_cert = GetProfileCertificate(profile_uuid); + + auto key = crypto::rsa::GenerateKey(rsaKeySizeBits); + + crypto::cert::CertData cert_data{}; + cert_data.Subject.CommonName = profile_uuid + "-onetime"; + cert_data.Subject.Country = profileCertCountry; + cert_data.Subject.State = profileCertState; + cert_data.Subject.Locality = profileCertLocality; + cert_data.Subject.Organization = profileCertOrganization; + cert_data.Subject.OrganizationUnit = profileCertOrganizationUnit; + cert_data.Email = email; + + cert_data.Validity.ValidYears = profileCertValidityYears; + cert_data.IsCA = false; + + auto issuer_cert = crypto::cert::ImportCertFromPEM(profile_cert->CertificatePEM.View()); + auto issuer_pkey = crypto::rsa::ImportPrivateKeyFromPEM(profile_cert->PrivateKeyPEM.View()); + + auto cert = crypto::cert::CreateCert(cert_data, issuer_cert.get(), issuer_pkey.get(), key.get()); + auto cert_bio = crypto::cert::ExportCertToPEM(cert.get()); + + auto key_bio = crypto::rsa::ExportPrivateKeyToPEM(key.get()); + + auto result = std::make_unique<ProfileCertificate>(); + result->PrivateKeyPEM = crypto::bio::View(key_bio.get()); + result->CertificatePEM = crypto::bio::View(cert_bio.get()); + + result->Chain.emplace_back(profile_cert->CertificatePEM.View()); + + for (const auto& chain_cert : profile_cert->Chain) { + result->Chain.emplace_back(chain_cert.View()); + } + + return result; +} + } // namespace vereign::identity diff --git a/cpp/src/vereign/identity/provider.hh b/cpp/src/vereign/identity/provider.hh index c526e41fa0e2353ca8dc2258609e9076a6a3f17a..b49471733a536a36de849377c6caaf41716ca597 100644 --- a/cpp/src/vereign/identity/provider.hh +++ b/cpp/src/vereign/identity/provider.hh @@ -1,12 +1,26 @@ #ifndef __VEREIGN_IDENTITY_PROVIDER_HH #define __VEREIGN_IDENTITY_PROVIDER_HH +#include <vereign/restapi/client_session.hh> #include <vereign/kvstore/crypto_storage.hh> #include <mutex> namespace vereign::identity { +/** + * A DTO representing a profile/onetime certificate with its private key, UUID and certificate chain. + * + * @see Provider::GetProfileCertificate + * @see Provider::GetProfileOneTimeCertificate + */ +struct ProfileCertificate { + bytes::Buffer PrivateKeyPEM; + bytes::Buffer CertificatePEM; + std::string CertificateUUID; + std::vector<bytes::Buffer> Chain; +}; + /** * Identity provider that manages the locally stored user identity. * @@ -19,7 +33,7 @@ public: * * @param storage The crypto storage used for read/write identity properties. */ - Provider(kvstore::CryptoStorage& storage); + Provider(restapi::ClientSession& client_session, kvstore::CryptoStorage& storage); /** * Default constructor. @@ -64,7 +78,66 @@ public: */ auto GetDeviceHash() -> std::string; + /** + * Retrieve profile certificate. + * + * When the profile certificate does not exist in the crypto storage just yet, a new private key + * and certificate is created and signed by Vereign server. Then the private key, the certificate + * and the issuers certificate chain are stored into the crypto storage. + * + * This means that the profile certificate is reused for the lifetime of the device registration. + * + * The profile certificate is a CA certificate and is used for signing one-time certificates. + * + * The `Common Name` of the certificate is derived from the + * `profile_uuid` - "<profile_uuid>-userdevice". + * + * Currently the profile certificate is part of the following certificate chain: + * 1. profile-userdevice + * 2. profile-serverside + * 3. vereign + * + * @param profile_uuid The profile UUID. + * @returns the profile certificate, its private key and the issuers certificate chain. + */ + auto GetProfileCertificate(const std::string& profile_uuid) -> std::unique_ptr<ProfileCertificate>; + + /** + * Creates and returns a one-time certificate for given profile. + * + * The one-time certificate is signed by the user-device profile certificate. + * + * When the profile certificate does not exist in the crypto storage just yet, a new private key + * and certificate is created and signed by Vereign server. Then the private key, the certificate + * and the issuers certificate chain are stored into the crypto storage. + * + * This means that the profile certificate is reused for the lifetime of the device registration. + * + * The one-time certificate is a leaf certificate and must be used one only sign operation. + * + * The `Common Name` of the certificate is derived from the + * `profile_uuid` - "<profile_uuid>-onetime". + * + * Currently the one-time certificate is part of the following certificate chain: + * 1. profile-onetime + * 2. profile-userdevice + * 3. profile-serverside + * 4. vereign + * + */ + auto GetProfileOneTimeCertificate( + const std::string& profile_uuid, + const std::string& email + ) -> std::unique_ptr<ProfileCertificate>; + private: + auto getProfileCertificateFromStorage( + const std::string& profile_uuid + ) -> std::unique_ptr<ProfileCertificate>; + +private: + restapi::ClientSession& client_session_; + std::mutex mu_; kvstore::CryptoStorage& storage_; diff --git a/cpp/src/vereign/kvstore/crypto_storage.cc b/cpp/src/vereign/kvstore/crypto_storage.cc index 7db16463cd4b0cc9947ff11dc2e9e56c54fb5a6c..1990c7f86211005f96ed3618757b1eeacf1af8ba 100644 --- a/cpp/src/vereign/kvstore/crypto_storage.cc +++ b/cpp/src/vereign/kvstore/crypto_storage.cc @@ -26,8 +26,8 @@ void CryptoStorage::PutBytes(const std::string& key, bytes::View value) { impl_->PutBytes(key, value); } -void CryptoStorage::GetBytes(const std::string& key, bytes::Buffer& value) { - impl_->GetBytes(key, value); +auto CryptoStorage::GetBytes(const std::string& key, bytes::Buffer& value) -> bool { + return impl_->GetBytes(key, value); } } // namespace vereign::identity diff --git a/cpp/src/vereign/kvstore/crypto_storage.hh b/cpp/src/vereign/kvstore/crypto_storage.hh index f0c03648c9d2afe06e7fde140157c691f66d954a..23f0e2fa5af3858d3d0677d012e00e6a3eed02f8 100644 --- a/cpp/src/vereign/kvstore/crypto_storage.hh +++ b/cpp/src/vereign/kvstore/crypto_storage.hh @@ -97,11 +97,10 @@ public: * @param value Buffer where the value will be returned. * * @throws StorageNotInitializedError when the storage is not initialized. - * @throws ValueNotFoundError when there is no value under the provided key. * @throws encoding::Error when the encrypted value cannot be decoded. * @throws crypto::OpenSSLError when the value cannot be decrypted. */ - void GetBytes(const std::string& key, bytes::Buffer& value); + auto GetBytes(const std::string& key, bytes::Buffer& value) -> bool; private: std::unique_ptr<detail::CryptoStorageImpl> impl_; diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc index 83bb4c7b1930b76829af0192b6357b674a42f7ae..44243c88497a01759a559f8e6ece6d430e5a237e 100644 --- a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc +++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc @@ -56,7 +56,7 @@ void BaseCryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) encryptBytes(key, value); } -void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) const { +auto BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) const -> bool { kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep}; if (key_.Size() == 0) { @@ -64,7 +64,10 @@ void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& valu } bytes::Buffer encoded; - storage_.GetBytes(key, encoded); + auto found = storage_.GetBytes(key, encoded); + if (!found) { + return false; + } bytes::View iv; bytes::View tag; @@ -72,6 +75,8 @@ void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& valu DecodeEncryptedValue(encoded.View(), iv, tag, encrypted); crypto::aes::GCM256Decrypt(encrypted, key_.View(), iv, tag, value); + + return true; } void BaseCryptoStorageImpl::initKey(bytes::Buffer&& key) { @@ -81,9 +86,8 @@ void BaseCryptoStorageImpl::initKey(bytes::Buffer&& key) { auto BaseCryptoStorageImpl::isTagValid() const -> bool { bytes::Buffer tag; try { - GetBytes("__tag", tag); + return GetBytes("__tag", tag); - return true; } catch (const crypto::Error&) { return false; } diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh index 49278c3f6847d4555dfd3ba58fc52a38a7d05e07..3039b8fe0fbb82f9207689dc9faa213795fab5c5 100644 --- a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh +++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh @@ -14,7 +14,7 @@ public: auto operator=(const BaseCryptoStorageImpl&) -> BaseCryptoStorageImpl& = delete; void PutBytes(const std::string& key, bytes::View value); - void GetBytes(const std::string& key, bytes::Buffer& value) const; + auto GetBytes(const std::string& key, bytes::Buffer& value) const -> bool; protected: void initKey(bytes::Buffer&& key); diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc index 1d59144d03098d016c2d7d1c29942fb7e95bac07..eb49ca7171ff49c3009c6bd5fbbf7cb9a9303e76 100644 --- a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc +++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc @@ -15,13 +15,15 @@ #include <chrono> namespace { - // FIXME: should these be injected and provided by the integrator - constexpr int iterations = 1 << 18; - constexpr int saltSizeBytes = 16; - constexpr int aesKeySizeBytes = 32; - constexpr int lockRetryCount = 10; - constexpr auto lockRetrySleep = std::chrono::milliseconds{1000}; +// FIXME: should these be injected and provided by the integrator +constexpr int iterations = 1 << 18; +constexpr int saltSizeBytes = 16; +constexpr int aesKeySizeBytes = 32; + +constexpr int lockRetryCount = 10; +constexpr auto lockRetrySleep = std::chrono::milliseconds{1000}; + } namespace vereign::kvstore::detail { @@ -40,16 +42,16 @@ void CryptoStorageImpl::Open(const std::string& pin) { kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep}; bytes::Buffer salt; - int64_t iterations = 0; + try { - iterations = storage_.GetInt64("__master_key_iterations"); + storage_.GetInt64("__master_key_iterations", iterations); storage_.GetBytes("__master_key_salt", salt); } catch (const std::exception& e) { throw StorageNotInitializedError{e.what()}; } - if (iterations == 0) { + if (iterations == 0 || salt.Size() == 0) { throw StorageNotInitializedError{"iterations cannot be zero"}; } diff --git a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc index 6d8651f38261282aa1de180d208e1a3506df1b94..1e78842bec9d48ad965f2486df9836da57d266d0 100644 --- a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc +++ b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc @@ -18,19 +18,21 @@ #include <chrono> namespace { - // FIXME: should these be injected and provided by the integrator - constexpr int keySizeBits = 2048; - constexpr int aesKeySizeBytes = 32; - - constexpr int lockRetryCount = 10; - constexpr auto lockRetrySleep = std::chrono::milliseconds{1000}; - - // FIXME: ask business for these values - constexpr const auto vereignKeyCreationTitle = std::string_view{"Vereign Client"}; - constexpr const auto vereignKeyDescription = std::string_view{ - "Vereign Client will use this key to authenticate with the Vereign Services" - }; - constexpr const auto vereignKeyFriendlyName = std::string_view{"Vereign Client Identity Key"}; + +// FIXME: should these be injected and provided by the integrator +constexpr int keySizeBits = 2048; +constexpr int aesKeySizeBytes = 32; + +constexpr int lockRetryCount = 10; +constexpr auto lockRetrySleep = std::chrono::milliseconds{1000}; + +// FIXME: ask business for these values +constexpr const auto vereignKeyCreationTitle = std::string_view{"Vereign Client"}; +constexpr const auto vereignKeyDescription = std::string_view{ + "Vereign Client will use this key to authenticate with the Vereign Services" +}; +constexpr const auto vereignKeyFriendlyName = std::string_view{"Vereign Client Identity Key"}; + } namespace vereign::kvstore::detail { diff --git a/cpp/src/vereign/kvstore/errors.hh b/cpp/src/vereign/kvstore/errors.hh index c119c510273cce58d67071e56da2ad16f43983da..50ec7271532c6b55cc6b8fc3adc7a1ddb1a569db 100644 --- a/cpp/src/vereign/kvstore/errors.hh +++ b/cpp/src/vereign/kvstore/errors.hh @@ -13,14 +13,6 @@ public: } }; -class ValueNotFoundError : public Error { -public: - ValueNotFoundError() - : Error{"value not found"} - { - } -}; - class StorageNotInitializedError : public Error { public: StorageNotInitializedError(const std::string& reason) diff --git a/cpp/src/vereign/kvstore/sqlite_storage.cc b/cpp/src/vereign/kvstore/sqlite_storage.cc index 4789346fef43cde99a8701c620d2d93eea7f0fb7..21371363d40fc84cb497c283b869b999baed8ab3 100644 --- a/cpp/src/vereign/kvstore/sqlite_storage.cc +++ b/cpp/src/vereign/kvstore/sqlite_storage.cc @@ -83,7 +83,7 @@ void SqliteStorage::PutBytes(const std::string& key, bytes::View value) { stmt.Step(); } -void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) { +auto SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) -> bool { kvstore::Lock l{*this}; auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?"); @@ -91,10 +91,12 @@ void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) { auto end = stmt.Step(); if (end) { - throw ValueNotFoundError{}; + return false; } value.Write(stmt.GetColumnBlob(0)); + + return true; } void SqliteStorage::PutInt64(const std::string& key, int64_t value) { @@ -109,7 +111,7 @@ void SqliteStorage::PutInt64(const std::string& key, int64_t value) { stmt.Step(); } -auto SqliteStorage::GetInt64(const std::string& key) -> int64_t { +auto SqliteStorage::GetInt64(const std::string& key, int64_t& value) -> bool { kvstore::Lock l{*this}; auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?"); @@ -117,17 +119,17 @@ auto SqliteStorage::GetInt64(const std::string& key) -> int64_t { auto end = stmt.Step(); if (end) { - throw ValueNotFoundError{}; + return false; } auto buf = stmt.GetColumnBlob(0); if (buf.Size() != 8) { - throw Error("cannot decode bytes size"); + throw Error("cannot decode int64 value"); } - int64_t result = encoding::binary::DecodeUint64(buf); + value = encoding::binary::DecodeUint64(buf); - return result; + return true; } } // namespace vereign::kvstore diff --git a/cpp/src/vereign/kvstore/sqlite_storage.hh b/cpp/src/vereign/kvstore/sqlite_storage.hh index 6f7d13992cb82dd6c1507e1f66183da9ca4a5012..6fdfce9d8e996351f99c85f0f9dc1f7e6c2525c8 100644 --- a/cpp/src/vereign/kvstore/sqlite_storage.hh +++ b/cpp/src/vereign/kvstore/sqlite_storage.hh @@ -72,10 +72,9 @@ public: * @param key The key of the value that will be retrieved. * @param value Buffer where the value will be returned. * - * @throws ValueNotFoundError when the key does not exist. * @throws sqlite::Error on failure. */ - void GetBytes(const std::string& key, bytes::Buffer& value) override; + auto GetBytes(const std::string& key, bytes::Buffer& value) -> bool override; /** * Store int64_t value into the storage. @@ -93,10 +92,9 @@ public: * @param key The key of the value that will be retrieved. * @param value Buffer where the value will be returned. * - * @throws ValueNotFoundError when the key does not exist. * @throws sqlite::Error on failure. */ - auto GetInt64(const std::string& key) -> int64_t override; + auto GetInt64(const std::string& key, int64_t& value) -> bool override; private: sqlite::Connection db_; diff --git a/cpp/src/vereign/kvstore/storage.hh b/cpp/src/vereign/kvstore/storage.hh index 2e8c55344f0c8481e924fb0c4673e263faa05585..ccb809468144a940243ee0e7d4769726056b8673 100644 --- a/cpp/src/vereign/kvstore/storage.hh +++ b/cpp/src/vereign/kvstore/storage.hh @@ -53,10 +53,8 @@ public: * * @param key The key of the value that will be retrieved. * @param value Buffer where the value will be returned. - * - * @throws ValueNotFoundError when the key does not exist. */ - virtual void GetBytes(const std::string& key, bytes::Buffer& value) = 0; + virtual auto GetBytes(const std::string& key, bytes::Buffer& value) -> bool = 0; /** * Store int64_t value into the storage. @@ -71,10 +69,8 @@ public: * * @param key The key of the value that will be retrieved. * @param value Buffer where the value will be returned. - * - * @throws ValueNotFoundError when the key does not exist. */ - virtual auto GetInt64(const std::string& key) -> int64_t = 0; + virtual auto GetInt64(const std::string& key, int64_t& value) -> bool = 0; /** * Destroy and cleanup. diff --git a/cpp/src/vereign/restapi/detail/post_task.hh b/cpp/src/vereign/restapi/detail/post_task.hh index 74f1ea70ae57b105207529f64c86aa27aaaa48bc..14d91fcc2411db7bbec59bfd9ef601b7dd8e8e26 100644 --- a/cpp/src/vereign/restapi/detail/post_task.hh +++ b/cpp/src/vereign/restapi/detail/post_task.hh @@ -125,6 +125,14 @@ public: private: void decodeErrorResponse(const HttpResponse& httpResp) { + if (httpResp.result() == beast::http::status::not_found) { + resp_->set_code("404"); + resp_->set_status(httpResp.body()); + resp_->set_error(httpResp.body()); + + return; + } + google::protobuf::util::JsonParseOptions options; client_library::ErrorStringResponse stringResp; diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 62bd6f48b027be1b1605a1f083be7c52d0ecaf00..c7bbbfcf2f6008b9cefbca1edce83566eed8f885 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -1,7 +1,20 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR} - "${VENDOR_INSTALL_DIR}/boost/include" +) + +set(TESTUTIL_SRC + experiment/array.cc + + testutil/protobuf.cc + testutil/golden.cc +) +add_library(testutil STATIC ${TESTUTIL_SRC}) +target_link_libraries(testutil PRIVATE + vereignlib +) +target_include_directories(testutil PRIVATE + ${CMAKE_SOURCE_DIR}/src ) add_subdirectory("protobuf") diff --git a/cpp/tests/integration/integration_test.cc b/cpp/tests/integration/integration_test.cc index e3db4498e0af99e6d0e70d425f56ee9a55933a91..482e13d76750910dcf20cf7a3c6e23142142d27a 100644 --- a/cpp/tests/integration/integration_test.cc +++ b/cpp/tests/integration/integration_test.cc @@ -85,8 +85,8 @@ TEST_CASE("C API integration", "[.integration]") { // std::cout << vereign::test::ProtobufToJson(getDIDsResp) << std::endl; - grpc_shutdown(); - google::protobuf::ShutdownProtobufLibrary(); + // grpc_shutdown(); + // google::protobuf::ShutdownProtobufLibrary(); } TEST_CASE("vereign_service_start") { diff --git a/cpp/tests/util/env.hh b/cpp/tests/testutil/env.hh similarity index 73% rename from cpp/tests/util/env.hh rename to cpp/tests/testutil/env.hh index d32fd97f8d66d0f25e76758425e426ae8b449079..fdb3d27635542fc84c448408fdd2ab152872f11e 100644 --- a/cpp/tests/util/env.hh +++ b/cpp/tests/testutil/env.hh @@ -1,10 +1,10 @@ -#ifndef __VEREIGN_TEST_UTIL_ENV_HH -#define __VEREIGN_TEST_UTIL_ENV_HH +#ifndef __VEREIGN_TESTUTIL_ENV_HH +#define __VEREIGN_TESTUTIL_ENV_HH #include <string> #include <fmt/core.h> -namespace vereign::test { +namespace vereign::testutil { inline auto RequireEnv(const std::string& name) -> std::string { auto var = std::getenv(name.c_str()); @@ -24,6 +24,6 @@ inline auto GetEnv(const std::string& name, const std::string& default_) -> std: return var; } -} // namespace test +} // namespace vereign::testutil -#endif // __VEREIGN_TEST_UTIL_ENV_HH +#endif // __VEREIGN_TESTUTIL_ENV_HH diff --git a/cpp/tests/util/error.hh b/cpp/tests/testutil/error.hh similarity index 53% rename from cpp/tests/util/error.hh rename to cpp/tests/testutil/error.hh index 85d7951d333c60aa7c222cab272ff6737798fd95..90a1fdb1beead8a790005b22c57baefc8cfcf08f 100644 --- a/cpp/tests/util/error.hh +++ b/cpp/tests/testutil/error.hh @@ -1,9 +1,9 @@ -#ifndef __VEREIGN_TEST_UTIL_ERROR_HH -#define __VEREIGN_TEST_UTIL_ERROR_HH +#ifndef __VEREIGN_TESTUTIL_ERROR_HH +#define __VEREIGN_TESTUTIL_ERROR_HH #include <optional> -namespace vereign::test { +namespace vereign::testutil { template <class Error, class Fun> auto CatchError(Fun fn) -> std::optional<Error> { @@ -17,6 +17,6 @@ auto CatchError(Fun fn) -> std::optional<Error> { } } -} // namespace test +} // namespace vereign::testutil -#endif // __VEREIGN_TEST_UTIL_ERROR_HH +#endif // __VEREIGN_TESTUTIL_ERROR_HH diff --git a/cpp/tests/testutil/golden.cc b/cpp/tests/testutil/golden.cc new file mode 100644 index 0000000000000000000000000000000000000000..e5a011263d8cf9211b0082da94c5b67c898e8204 --- /dev/null +++ b/cpp/tests/testutil/golden.cc @@ -0,0 +1,26 @@ +#include <testutil/golden.hh> + +#include <vereign/fs/util.hh> +#include <testutil/env.hh> + +#include <catch2/catch.hpp> + +namespace { + constexpr const char* GoldenUpdateEnvVar = "GOLDEN_UPDATE"; +} + +namespace vereign::testutil::golden { + +void Assert(const std::string& golden_file_path, bytes::View src) { + auto update = vereign::testutil::GetEnv(GoldenUpdateEnvVar, ""); + if (!update.empty()) { + fs::WriteFile(golden_file_path, src); + } + + auto data = fs::ReadFile(golden_file_path); + + // TODO: create nice patch like diff on failure + CHECK(data.View().String() == src.String()); +} + +} // namespace vereign::test diff --git a/cpp/tests/testutil/golden.hh b/cpp/tests/testutil/golden.hh new file mode 100644 index 0000000000000000000000000000000000000000..2fb33bc97c6a742d35a6f569fa026431ece91a11 --- /dev/null +++ b/cpp/tests/testutil/golden.hh @@ -0,0 +1,13 @@ +#ifndef __VEREIGN_TESTUTIL_GOLDEN_HH +#define __VEREIGN_TESTUTIL_GOLDEN_HH + +#include <vereign/bytes/view.hh> +#include <string> + +namespace vereign::testutil::golden { + +void Assert(const std::string& golden_file, bytes::View src); + +} // namespace vereign::test + +#endif // __VEREIGN_TESTUTIL_GOLDEN_HH diff --git a/cpp/tests/util/protobuf.cc b/cpp/tests/testutil/protobuf.cc similarity index 74% rename from cpp/tests/util/protobuf.cc rename to cpp/tests/testutil/protobuf.cc index 05e1c791a91878f1d09a4c0068227de89e7937e1..c6ecf9991f052196651cb71af76dbf7598b1ac94 100644 --- a/cpp/tests/util/protobuf.cc +++ b/cpp/tests/testutil/protobuf.cc @@ -1,12 +1,11 @@ #include <vereign/grpc/json/encoder.hh> -#include <util/protobuf.hh> +#include <testutil/protobuf.hh> #include <google/protobuf/util/json_util.h> #include <catch2/catch.hpp> -namespace vereign { -namespace test { +namespace vereign::testutil { auto ProtobufToJson(const google::protobuf::Message& msg) -> std::string { std::string result; @@ -15,5 +14,4 @@ auto ProtobufToJson(const google::protobuf::Message& msg) -> std::string { return result; } -} // namespace vereign -} // namespace test +} // namespace vereign::testutil diff --git a/cpp/tests/testutil/protobuf.hh b/cpp/tests/testutil/protobuf.hh new file mode 100644 index 0000000000000000000000000000000000000000..264f7f6e51011c8678e68e0d943176ab0217e497 --- /dev/null +++ b/cpp/tests/testutil/protobuf.hh @@ -0,0 +1,13 @@ +#ifndef __VEREIGN_TEST_UTIL_PROTOBUF_HH +#define __VEREIGN_TEST_UTIL_PROTOBUF_HH + +#include <string> +#include <google/protobuf/message.h> + +namespace vereign::testutil { + +auto ProtobufToJson(const google::protobuf::Message& msg) -> std::string; + +} // namespace vereign::testutil + +#endif // __VEREIGN_TESTUTIL_PROTOBUF_HH diff --git a/cpp/tests/util/protobuf.hh b/cpp/tests/util/protobuf.hh deleted file mode 100644 index 09ca9be1f1b36d9d1444160333d0017ffc396b8e..0000000000000000000000000000000000000000 --- a/cpp/tests/util/protobuf.hh +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef __VEREIGN_TEST_UTIL_PROTOBUF_HH -#define __VEREIGN_TEST_UTIL_PROTOBUF_HH - -#include <string> -#include <google/protobuf/message.h> - -namespace vereign { -namespace test { - -std::string ProtobufToJson(const google::protobuf::Message& msg); - -} // namespace vereign -} // namespace test - -#endif // __VEREIGN_TEST_UTIL_PROTOBUF_HH diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt index 0bcfbc6e919333d7cae2525229b4c53b0cf21247..b7c43c069858265bed7a23715e8832f9b4505105 100644 --- a/cpp/tests/vereign/CMakeLists.txt +++ b/cpp/tests/vereign/CMakeLists.txt @@ -12,11 +12,10 @@ include_directories( list(APPEND TESTS_SRC init_tests.cc - ../util/protobuf.cc - ../experiment/array.cc test/device.cc test/service_context.cc + core/time_test.cc sync/channel_test.cc encoding/base64_test.cc @@ -25,9 +24,11 @@ list(APPEND TESTS_SRC bytes/buffer_test.cc bytes/view_test.cc + crypto/errors_test.cc crypto/aes_test.cc crypto/rsa_test.cc crypto/digest_test.cc + crypto/cert_test.cc restapi/client_test.cc restapi/client_session_test.cc @@ -64,7 +65,7 @@ target_proto_generate( target_link_libraries(tests vereignlib - Threads::Threads + testutil ) if (VEREIGN_ENABLE_BENCHMARKING) diff --git a/cpp/tests/vereign/core/time_test.cc b/cpp/tests/vereign/core/time_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..09637b9fb892cc8a86a27281587d003326c15642 --- /dev/null +++ b/cpp/tests/vereign/core/time_test.cc @@ -0,0 +1,42 @@ +#include <vereign/core/time.hh> + +#include <catch2/catch.hpp> + +using namespace vereign; + +TEST_CASE("time::MakePosixTime", "[vereign/time][vereign/core]") { + auto ptime = time::MakePosixTime(2020, 1, 1, 0, 1, 1); + + CHECK(ptime.date().year() == 2020); + CHECK(ptime.date().month() == boost::gregorian::Jan); + CHECK(ptime.date().day() == 1); + CHECK(ptime.time_of_day().hours() == 0); + CHECK(ptime.time_of_day().minutes() == 1); + CHECK(ptime.time_of_day().seconds() == 1); + + CHECK(time::PosixTimeToTime(ptime) == 1577836861); + CHECK(time::MakeTimeUTCFromISO("20200101T000101") == time::PosixTimeToTime(ptime)); + + ptime = time::MakePosixTime(2020, 1, 1, 24, 60, 60); + CHECK(boost::posix_time::to_iso_string(ptime) == "20200102T010100"); +} + +TEST_CASE("time::MakeTimeUTC", "[vereign/time][vereign/core]") { + auto timestamp = time::MakeTimeUTC(2020, 1, 1, 0, 1, 1); + + CHECK(timestamp == 1577836861); + CHECK(time::MakeTimeUTCFromISO("20200101T000101") == timestamp); +} + +TEST_CASE("time::MakeTimeUTCFromString", "[vereign/time][vereign/core]") { + auto timestamp = time::MakeTimeUTCFromString("2020-1-1 0:1:1"); + + CHECK(timestamp == 1577836861); + CHECK(time::MakeTimeUTCFromISO("20200101T000101") == timestamp); +} + +TEST_CASE("time::MakeTimeUTCFromISO", "[vereign/time][vereign/core]") { + auto timestamp = time::MakeTimeUTCFromISO("20200101T000101"); + + CHECK(timestamp == 1577836861); +} diff --git a/cpp/tests/vereign/crypto/aes_test.cc b/cpp/tests/vereign/crypto/aes_test.cc index cfbb694c4e2eaddfd922670ad528a6984ebb0850..b9f4c41bd0334cd29d4a75e839572bf1f4ea8ba5 100644 --- a/cpp/tests/vereign/crypto/aes_test.cc +++ b/cpp/tests/vereign/crypto/aes_test.cc @@ -8,7 +8,7 @@ using namespace vereign; -TEST_CASE("aes::GCM256Encrypt", "[vereign/crypto/aes][vereign/crypto]") { +TEST_CASE("crypto::aes::GCM256Encrypt", "[vereign/crypto/aes][vereign/crypto]") { const std::string data{"foo bar"}; auto key = crypto::Rand(32); diff --git a/cpp/tests/vereign/crypto/cert_test.cc b/cpp/tests/vereign/crypto/cert_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..da6bec076b79072fff0f925e9f20965b3ec84927 --- /dev/null +++ b/cpp/tests/vereign/crypto/cert_test.cc @@ -0,0 +1,84 @@ +#include <vereign/crypto/cert.hh> + +#include <vereign/core/time.hh> +#include <vereign/crypto/rsa.hh> +#include <vereign/crypto/rand.hh> +#include <vereign/crypto/bio.hh> +#include <vereign/bytes/view_dump.hh> +#include <vereign/fs/path.hh> +#include <vereign/fs/util.hh> +#include <testutil/golden.hh> + +#include <catch2/catch.hpp> + +#include <iostream> +#include <fstream> + +using namespace vereign; + +namespace { + const std::string testDataPath = fs::path::Join("..", "tests", "vereign", "crypto", "test_data"); +} + +TEST_CASE("crypto::cert::CreateSelfSignedCert", "[vereign/crypto/cert][vereign/crypto]") { + auto key = crypto::rsa::ImportPrivateKeyFromPEM( + fs::ReadFile(fs::path::Join(testDataPath, "ca_key.pem")).View() + ); + + crypto::cert::CertData cert_data{}; + cert_data.Email = "ca@example.com"; + cert_data.Subject.CommonName = "ca-cn.example.com"; + cert_data.Subject.Country = "BG"; + cert_data.Subject.State = "Sofia"; + cert_data.Subject.Locality = "Sofia"; + cert_data.Subject.Organization = "Vereign AG"; + cert_data.Subject.OrganizationUnit = "CA"; + cert_data.Url = "ca.example.com"; + cert_data.Validity.ValidYears = 5; + cert_data.SerialNumber = 42; + cert_data.Validity.NotBefore = vereign::time::MakeTimeUTCFromString("2020-07-01 00:00:00"); + cert_data.IsCA = true; + + auto cert = crypto::cert::CreateSelfSignedCert(cert_data, key.get()); + + std::stringstream ss; + crypto::cert::PrintCert(ss, cert.get()); + testutil::golden::Assert(fs::path::Join(testDataPath, "ca_cert.txt"), bytes::View(ss.str())); + + auto pem = crypto::cert::ExportCertToPEM(cert.get()); + testutil::golden::Assert(fs::path::Join(testDataPath, "ca.crt"), crypto::bio::View(pem.get())); +} + +TEST_CASE("crypto::cert::CreateCert", "[vereign/crypto/cert][vereign/crypto]") { + auto ca_key = crypto::rsa::ImportPrivateKeyFromPEM( + fs::ReadFile(fs::path::Join(testDataPath, "ca_key.pem")).View() + ); + + auto ca_cert = crypto::cert::ImportCertFromPEM( + fs::ReadFile(fs::path::Join(testDataPath, "ca.crt")).View() + ); + + auto key = crypto::rsa::ImportPrivateKeyFromPEM( + fs::ReadFile(fs::path::Join(testDataPath, "leaf_key.pem")).View() + ); + + crypto::cert::CertData cert_data{}; + cert_data.Email = "test@example.com"; + cert_data.Subject.CommonName = "example.com"; + cert_data.Subject.Country = "BG"; + cert_data.Subject.State = "Sofia"; + cert_data.Subject.Locality = "Sofia"; + cert_data.Subject.Organization = "Vereign AG"; + cert_data.Subject.OrganizationUnit = "Engineering"; + cert_data.Url = "api.example.com"; + cert_data.Validity.ValidYears = 5; + cert_data.SerialNumber = 73; + cert_data.Validity.NotBefore = vereign::time::MakeTimeUTCFromString("2020-07-02 00:00:00"); + + auto cert = crypto::cert::CreateCert(cert_data, ca_cert.get(), ca_key.get(), key.get()); + + std::stringstream ss; + crypto::cert::PrintCert(ss, cert.get()); + + testutil::golden::Assert(fs::path::Join(testDataPath, "leaf_cert.txt"), bytes::View(ss.str())); +} diff --git a/cpp/tests/vereign/crypto/digest_test.cc b/cpp/tests/vereign/crypto/digest_test.cc index 0bf6018f3bcc7c1120dc699e8d80024f2c4f3cbb..cea92371b19c2290900f0047a587a54522ad537b 100644 --- a/cpp/tests/vereign/crypto/digest_test.cc +++ b/cpp/tests/vereign/crypto/digest_test.cc @@ -9,7 +9,7 @@ using namespace vereign; -TEST_CASE("digest::sha1", "[vereign/crypto/digest][vereign/crypto]") { +TEST_CASE("crypto::digest::sha1", "[vereign/crypto/digest][vereign/crypto]") { const std::string data{"foo bar"}; bytes::Buffer hash; diff --git a/cpp/tests/vereign/crypto/errors_test.cc b/cpp/tests/vereign/crypto/errors_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..1b9f32a3523e27b14a9985047ccb0caca2f1dd46 --- /dev/null +++ b/cpp/tests/vereign/crypto/errors_test.cc @@ -0,0 +1,24 @@ +#include <vereign/crypto/errors.hh> +#include <openssl/evp.h> + +#include <catch2/catch.hpp> + +using namespace vereign; + +TEST_CASE("crypto::OpenSSLError", "[vereign/crypto]") { + SECTION("it must return error with message that included the last OpenSSL error") { + EVP_PKEY_CTX* ctx = nullptr; + auto r = EVP_PKEY_encrypt_init(ctx); + CHECK(r == 0); + + auto err = crypto::OpenSSLError{"pkey encrypt failed"}; + CHECK(std::string(err.what()) == "pkey encrypt failed: OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE"); + } + + SECTION("when there was no OpenSSL error," + " it must create an error with `no openssl error in the error queue`") { + auto err = crypto::OpenSSLError{"test error"}; + + CHECK(std::string(err.what()) == "test error: no openssl error in the error queue"); + } +} diff --git a/cpp/tests/vereign/crypto/test_data/.gitattributes b/cpp/tests/vereign/crypto/test_data/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..fa1385d99a319b43c06f5309d1aae9fdd3adea46 --- /dev/null +++ b/cpp/tests/vereign/crypto/test_data/.gitattributes @@ -0,0 +1 @@ +* -text diff --git a/cpp/tests/vereign/crypto/test_data/ca.crt b/cpp/tests/vereign/crypto/test_data/ca.crt new file mode 100644 index 0000000000000000000000000000000000000000..affc0044449d58fa031c69a13afa931baf587d3e --- /dev/null +++ b/cpp/tests/vereign/crypto/test_data/ca.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEvDCCA6SgAwIBAgIBKjANBgkqhkiG9w0BAQsFADCBijEaMBgGA1UEAwwRY2Et +Y24uZXhhbXBsZS5jb20xCzAJBgNVBAYTAkJHMQ4wDAYDVQQHDAVTb2ZpYTEOMAwG +A1UECAwFU29maWExEzARBgNVBAoMClZlcmVpZ24gQUcxCzAJBgNVBAsMAkNBMR0w +GwYJKoZIhvcNAQkBFg5jYUBleGFtcGxlLmNvbTAeFw0yMDA3MDEwMDAwMDBaFw0y +NTA2MzAwMDAwMDBaMIGKMRowGAYDVQQDDBFjYS1jbi5leGFtcGxlLmNvbTELMAkG +A1UEBhMCQkcxDjAMBgNVBAcMBVNvZmlhMQ4wDAYDVQQIDAVTb2ZpYTETMBEGA1UE +CgwKVmVyZWlnbiBBRzELMAkGA1UECwwCQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4 +YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxINphs0i +1HrDcvsG1mUnNrcnwhyitN5ZX8FDgtk+r5RXNk+zRvmlDz+kKq/UNXgIH5v1LbRi +Vcmh9njnLqh4uPW2Khkxna3yjnWF3NqV780uOCkUEZp0CC2B2RRXCjmxF3wBtggb +FjBSoh5nhYUtJZkeDgY+AaFlfBKHWWQpzLJWxdHFW/Ej33MubilPTKOaP2Nh9U9x +bxiwAHS3ijnhpW2Xh6uwf1XMQVwwsbPtEd81BBU86KIeQzOQrYkUEl81nqOzt9Ct +67fUJs0yPPTtoEekWAYCdFD0hrVLxz1ilQtYF+shXL/6GyrKz0putQhO2wOqINXV +rHUv1SgEMwD/TwIDAQABo4IBKTCCASUwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAYYwKQYDVR0RBCIwIIEOY2FAZXhhbXBsZS5jb22CDmNhLmV4YW1wbGUu +Y29tMB0GA1UdDgQWBBR+ivGqlvajtAUhWQthghHP6zZrdjCBtwYDVR0jBIGvMIGs +gBR+ivGqlvajtAUhWQthghHP6zZrdqGBkKSBjTCBijEaMBgGA1UEAwwRY2EtY24u +ZXhhbXBsZS5jb20xCzAJBgNVBAYTAkJHMQ4wDAYDVQQHDAVTb2ZpYTEOMAwGA1UE +CAwFU29maWExEzARBgNVBAoMClZlcmVpZ24gQUcxCzAJBgNVBAsMAkNBMR0wGwYJ +KoZIhvcNAQkBFg5jYUBleGFtcGxlLmNvbYIBKjANBgkqhkiG9w0BAQsFAAOCAQEA +wHKy0Gmj5Gs+DglsqzOfmUeqy8EsmaS6ufxV084umpnNmaQrdBSQFH0P8sQJROpH +1bgibRLQFwQLKvAC6dxCvnH6njW0lCZ/53OkrNyky53eZ6I6DHrT0kjkC9KqShEf ++MhHBniVCX2w8HxFgZgMKGiwL+D8qo5QzHmR+AzXAZGlKKZReKuPUehfO02cVWPH +uz/dSl2gd7xdAz3lwayVPiNNCu4r0/gwvEiHuwCvjIQPIxsUBlm5BwB0ga0OlMJu +nGVv3dNkS2KjIEBms4jvXxk9w51OQqrcVUhuj8qHenTtM6ho5QTtDi2z/NbM0oW1 +zAmLUoPW97IgXtp4ghOQ7w== +-----END CERTIFICATE----- diff --git a/cpp/tests/vereign/crypto/test_data/ca_cert.txt b/cpp/tests/vereign/crypto/test_data/ca_cert.txt new file mode 100644 index 0000000000000000000000000000000000000000..9ed51e64c82cf5d0d352d31b632f74c18daa62e8 --- /dev/null +++ b/cpp/tests/vereign/crypto/test_data/ca_cert.txt @@ -0,0 +1,63 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 42 (0x2a) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=ca-cn.example.com, C=BG, L=Sofia, ST=Sofia, O=Vereign AG, OU=CA/emailAddress=ca@example.com + Validity + Not Before: Jul 1 00:00:00 2020 GMT + Not After : Jun 30 00:00:00 2025 GMT + Subject: CN=ca-cn.example.com, C=BG, L=Sofia, ST=Sofia, O=Vereign AG, OU=CA/emailAddress=ca@example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:c4:83:69:86:cd:22:d4:7a:c3:72:fb:06:d6:65: + 27:36:b7:27:c2:1c:a2:b4:de:59:5f:c1:43:82:d9: + 3e:af:94:57:36:4f:b3:46:f9:a5:0f:3f:a4:2a:af: + d4:35:78:08:1f:9b:f5:2d:b4:62:55:c9:a1:f6:78: + e7:2e:a8:78:b8:f5:b6:2a:19:31:9d:ad:f2:8e:75: + 85:dc:da:95:ef:cd:2e:38:29:14:11:9a:74:08:2d: + 81:d9:14:57:0a:39:b1:17:7c:01:b6:08:1b:16:30: + 52:a2:1e:67:85:85:2d:25:99:1e:0e:06:3e:01:a1: + 65:7c:12:87:59:64:29:cc:b2:56:c5:d1:c5:5b:f1: + 23:df:73:2e:6e:29:4f:4c:a3:9a:3f:63:61:f5:4f: + 71:6f:18:b0:00:74:b7:8a:39:e1:a5:6d:97:87:ab: + b0:7f:55:cc:41:5c:30:b1:b3:ed:11:df:35:04:15: + 3c:e8:a2:1e:43:33:90:ad:89:14:12:5f:35:9e:a3: + b3:b7:d0:ad:eb:b7:d4:26:cd:32:3c:f4:ed:a0:47: + a4:58:06:02:74:50:f4:86:b5:4b:c7:3d:62:95:0b: + 58:17:eb:21:5c:bf:fa:1b:2a:ca:cf:4a:6e:b5:08: + 4e:db:03:aa:20:d5:d5:ac:75:2f:d5:28:04:33:00: + ff:4f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + X509v3 Subject Alternative Name: + email:ca@example.com, DNS:ca.example.com + X509v3 Subject Key Identifier: + 7E:8A:F1:AA:96:F6:A3:B4:05:21:59:0B:61:82:11:CF:EB:36:6B:76 + X509v3 Authority Key Identifier: + keyid:7E:8A:F1:AA:96:F6:A3:B4:05:21:59:0B:61:82:11:CF:EB:36:6B:76 + DirName:/CN=ca-cn.example.com/C=BG/L=Sofia/ST=Sofia/O=Vereign AG/OU=CA/emailAddress=ca@example.com + serial:2A + + Signature Algorithm: sha256WithRSAEncryption + c0:72:b2:d0:69:a3:e4:6b:3e:0e:09:6c:ab:33:9f:99:47:aa: + cb:c1:2c:99:a4:ba:b9:fc:55:d3:ce:2e:9a:99:cd:99:a4:2b: + 74:14:90:14:7d:0f:f2:c4:09:44:ea:47:d5:b8:22:6d:12:d0: + 17:04:0b:2a:f0:02:e9:dc:42:be:71:fa:9e:35:b4:94:26:7f: + e7:73:a4:ac:dc:a4:cb:9d:de:67:a2:3a:0c:7a:d3:d2:48:e4: + 0b:d2:aa:4a:11:1f:f8:c8:47:06:78:95:09:7d:b0:f0:7c:45: + 81:98:0c:28:68:b0:2f:e0:fc:aa:8e:50:cc:79:91:f8:0c:d7: + 01:91:a5:28:a6:51:78:ab:8f:51:e8:5f:3b:4d:9c:55:63:c7: + bb:3f:dd:4a:5d:a0:77:bc:5d:03:3d:e5:c1:ac:95:3e:23:4d: + 0a:ee:2b:d3:f8:30:bc:48:87:bb:00:af:8c:84:0f:23:1b:14: + 06:59:b9:07:00:74:81:ad:0e:94:c2:6e:9c:65:6f:dd:d3:64: + 4b:62:a3:20:40:66:b3:88:ef:5f:19:3d:c3:9d:4e:42:aa:dc: + 55:48:6e:8f:ca:87:7a:74:ed:33:a8:68:e5:04:ed:0e:2d:b3: + fc:d6:cc:d2:85:b5:cc:09:8b:52:83:d6:f7:b2:20:5e:da:78: + 82:13:90:ef diff --git a/cpp/tests/vereign/crypto/test_data/ca_key.pem b/cpp/tests/vereign/crypto/test_data/ca_key.pem new file mode 100644 index 0000000000000000000000000000000000000000..2c73e6ead4478b7c4d32a2ad3b3031c7eff054e0 --- /dev/null +++ b/cpp/tests/vereign/crypto/test_data/ca_key.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDEg2mGzSLUesNy ++wbWZSc2tyfCHKK03llfwUOC2T6vlFc2T7NG+aUPP6Qqr9Q1eAgfm/UttGJVyaH2 +eOcuqHi49bYqGTGdrfKOdYXc2pXvzS44KRQRmnQILYHZFFcKObEXfAG2CBsWMFKi +HmeFhS0lmR4OBj4BoWV8EodZZCnMslbF0cVb8SPfcy5uKU9Mo5o/Y2H1T3FvGLAA +dLeKOeGlbZeHq7B/VcxBXDCxs+0R3zUEFTzooh5DM5CtiRQSXzWeo7O30K3rt9Qm +zTI89O2gR6RYBgJ0UPSGtUvHPWKVC1gX6yFcv/obKsrPSm61CE7bA6og1dWsdS/V +KAQzAP9PAgMBAAECggEAOBj9hGZF9ZsP9WBUrNpWbeeuGVscVX6Ny+h7UbybiPrT +RKVO28mDRY2Y2zizXwojY4adYI04bx3uttH/yNF+GOrHtE/Z2pXgAqvo6Uma+Mg9 +U/niCT5qtjTrduE4Eaqzc61KPcElnllwFWiRi8ufPjbuHfnJNKTyuA8ABVQzHIQi +Z+Rhw/IASS2aMuD1A4XicneZ43Vo83mz7xfiebVW7WRyIIcxlWruHQM+BYCTR+y5 +vGdq+P0e4ZjAdwudxB6MfmF5lO16M8CiH5SAJghmBkbEpwdkZuMX6YQLGnw2+C6m +NHRRb0WSGX12PEJcxA4mvM4L2+m7ihZTSJg5fYSLAQKBgQDmV77TXc6xAfq1O3es +AV69a5rNgtJ3I7wnv42ZLpQLVSVQ1eqRzhaMIItbX3hwb9STWagIuuGbTG590k00 +lsHRD0/Z4mapyk43b9Zs90ug1L0ojqrVfmjb5uJDzj4fbHO1QtJ2Dt2llMveGCaE +j5LYz+AKdBjZdxxU6NDws8W1AQKBgQDaZwQjMAx7usp+prVVe33QchTxeziaFh+L +q53zimoCsXjTSz040dkdk+yhj0DOT3z4Of41+PkWmbLoZh1ar9yFSQPC3fVvz4Sx +mY5gCIy1D9a3SSPYy6+V6PiCSkYB3JsGONKARl1oxNswewGcBVSxEYMH0INGccoc +Aqv0WYokTwKBgE/QtdHd3oIdUnc8HPKgIuj2AVUW8MDRxB/t0y3yIuBuZ5jEFxzg +0ZVKrZ9CzKQBTCKm3X/w6b37VQoKNjBz0MMVUDEKr1JERKgW+7EUW8NmFAmarf1+ +aO1R6rNeIs6WsN1tQyofapQ2pBHYQniKm3K/TT2yDu5DxLBzX/sX//kBAoGAfNsL +uHoNiOZ+QO1ZGSdiA7pUUsDY2O3w1s4jnEjy1dtTcae/ti3xa0FXs/zLVaaHkPjG +ZWCSNeNkLgvCmEeVT/Uv20U8vNDr0QQyKqB4oEIPw/RiM9wt4wuulf2UDBhr36C4 +ZnDTkcOIcyy+/MznX9rdB6iig7Elk+HLIAPYP20CgYAL+/AizILmdI7lBvderLfw +2oslABhYJCjaFX5XWTvOJpPdZPZSqZUgSvzgBM57f1gtRmn6NS1oM6qagoYlOpF4 +rjIcuz86j8tFTp45TjMz1Sf3afyi+iiFhdjTeDTdyjcWK0sBghyljc4jq5scQsYH +SUOdYY70Kenpzx6iOaWe/g== +-----END PRIVATE KEY----- + diff --git a/cpp/tests/vereign/crypto/test_data/leaf_cert.txt b/cpp/tests/vereign/crypto/test_data/leaf_cert.txt new file mode 100644 index 0000000000000000000000000000000000000000..fb718f515d1240b1ea27e79859552b2d87d7c06e --- /dev/null +++ b/cpp/tests/vereign/crypto/test_data/leaf_cert.txt @@ -0,0 +1,65 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 73 (0x49) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=ca-cn.example.com, C=BG, L=Sofia, ST=Sofia, O=Vereign AG, OU=CA/emailAddress=ca@example.com + Validity + Not Before: Jul 2 00:00:00 2020 GMT + Not After : Jul 1 00:00:00 2025 GMT + Subject: CN=example.com, C=BG, L=Sofia, ST=Sofia, O=Vereign AG, OU=Engineering/emailAddress=test@example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:be:ab:51:25:32:3c:75:bb:49:25:1b:b9:9f:6e: + af:ff:38:07:f0:2c:49:e7:3b:c8:3a:19:02:f1:aa: + 96:8d:9d:bd:37:b0:97:2a:11:05:71:65:94:ee:32: + 6b:4b:d7:94:43:a1:8c:bb:ed:fe:94:d6:93:f8:7b: + ca:48:7d:fc:99:00:88:d5:d1:73:55:31:ca:eb:69: + 5f:0d:1b:6a:24:39:db:b9:f1:7a:34:c7:1f:8e:a4: + 12:ab:1d:e8:29:b6:aa:05:d3:fb:7d:bb:7e:2b:db: + c8:18:15:ba:8e:6d:b4:df:98:3c:34:6c:50:07:e9: + 7a:7a:57:99:8b:cf:6d:d7:c3:e1:c5:90:ae:d3:94: + dc:b9:fc:90:20:ec:63:24:e5:8b:42:d3:bb:4d:e6: + e2:f0:b6:48:a9:c0:27:2e:89:bc:45:db:b6:25:be: + 7d:55:40:6d:72:e4:5b:49:81:d4:aa:e0:3a:33:93: + f9:4b:b2:82:b4:45:e1:5e:0c:14:b0:6e:78:f9:1a: + 98:72:8c:ef:c5:10:ab:7f:9d:e7:9b:68:17:30:21: + f0:95:bf:ab:c6:45:bb:3c:cf:f1:96:1f:8d:22:88: + 72:b3:52:ff:20:c0:40:5b:11:0d:62:24:ec:5c:53: + 81:fa:ca:ec:a5:00:b8:7c:1e:e2:a8:b7:01:42:5e: + 1d:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Key Usage: critical + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment + X509v3 Extended Key Usage: + E-mail Protection + X509v3 Subject Alternative Name: + email:test@example.com, DNS:api.example.com + X509v3 Subject Key Identifier: + A5:BD:8A:35:84:98:B6:6C:13:51:A2:4A:5D:C9:51:C6:61:7E:5D:AE + X509v3 Authority Key Identifier: + keyid:7E:8A:F1:AA:96:F6:A3:B4:05:21:59:0B:61:82:11:CF:EB:36:6B:76 + DirName:/CN=ca-cn.example.com/C=BG/L=Sofia/ST=Sofia/O=Vereign AG/OU=CA/emailAddress=ca@example.com + serial:2A + + Signature Algorithm: sha256WithRSAEncryption + 8d:90:e7:a7:a6:57:ec:b3:db:38:37:87:bc:1e:0b:e2:bd:c4: + 88:e3:95:f0:35:01:a7:fe:51:92:b1:4c:37:d7:21:cf:80:77: + 51:98:62:61:2c:99:58:60:89:01:35:3e:56:8c:dd:07:21:ad: + a1:e3:e5:02:3c:e5:cf:c8:ee:46:11:07:16:f5:37:8c:75:22: + e0:af:85:e2:85:31:21:a2:f3:e4:b5:1b:de:ef:31:86:88:84: + 18:11:bc:67:5a:a7:0b:d1:16:fc:5c:12:51:8d:1c:26:d5:45: + f8:55:28:7f:85:a2:c7:a7:e4:bc:6b:98:b4:c4:d9:3e:f6:72: + a8:f9:24:f5:9b:38:bf:aa:76:9c:68:c8:67:4f:fa:79:9c:7c: + 52:f2:45:6c:05:26:4f:64:4c:ea:52:d0:c2:75:45:21:e5:8e: + d7:60:0a:b2:6c:d2:f8:1c:01:9b:b1:72:1d:4e:95:79:18:34: + b6:1c:bc:ea:5d:12:1f:33:90:63:dc:01:b9:9b:33:dd:62:31: + 69:c8:b2:1d:ce:3d:38:57:60:c5:75:7d:84:95:3d:db:36:0a: + a3:77:7c:67:b6:e3:7a:c0:b4:a5:51:05:78:6b:29:28:43:b6: + 74:d8:74:18:8e:4f:44:62:55:49:bb:f9:f3:1b:37:63:49:ff: + c9:2e:73:a2 diff --git a/cpp/tests/vereign/crypto/test_data/leaf_key.pem b/cpp/tests/vereign/crypto/test_data/leaf_key.pem new file mode 100644 index 0000000000000000000000000000000000000000..5d83ade2c640588ac9df74b38aa7dd62dc23600c --- /dev/null +++ b/cpp/tests/vereign/crypto/test_data/leaf_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+q1ElMjx1u0kl +G7mfbq//OAfwLEnnO8g6GQLxqpaNnb03sJcqEQVxZZTuMmtL15RDoYy77f6U1pP4 +e8pIffyZAIjV0XNVMcrraV8NG2okOdu58Xo0xx+OpBKrHegptqoF0/t9u34r28gY +FbqObbTfmDw0bFAH6Xp6V5mLz23Xw+HFkK7TlNy5/JAg7GMk5YtC07tN5uLwtkip +wCcuibxF27Ylvn1VQG1y5FtJgdSq4Dozk/lLsoK0ReFeDBSwbnj5GphyjO/FEKt/ +neebaBcwIfCVv6vGRbs8z/GWH40iiHKzUv8gwEBbEQ1iJOxcU4H6yuylALh8HuKo +twFCXh0BAgMBAAECggEAAuEIMLktYl4MLh2DpBrng5bMwI1mpfFy5fUD5J1zDbCJ ++iSLj8oh1yhrGIgAiJdjfibwaVO1Z6Qa0rfJzOZBcWz+4/L4RxshSYSLN8yIFy5x +eSwNMJf+IROsqCf43g0LobAQU4HIggXc+Qd2TEQZ+lNXO2Jpo0jyUr/DtNxSYrLk +PQOiZUeTRApEFMH/KR7E3CINnFWEsyBktr4UGNT+KbuS+h24QFgeMZS03ELCER/E +GtYgjYE8Njb+LLC82ZB0jVobo7sXqEu4xhunsysJMID7XF7cRbVH4jS4owBik+MM +0C4x3IWcUlPTRwCwz7ThdNmuaWysdcm6YKmDTjkg7QKBgQDgObGyYADOBVC986xD +rHQ6HsHjJoS7UA98OKtpwUfoHgmzCn787bL+28payYLjBQg8Lu0V4wz6IzryCjEY +DBDWT376lsIElYQBVO80JR+sQHveRKHj3nKxin8RFv+P1iR5l06MLFuI7lpgnfjf +zAJAMtob6hXTfhKd6ttw3LMJtQKBgQDZsEsv3iWEtJOlEk32b+cBtC9m3+3BRrsl +vhMqJRRiz3q8ESw4UeYnjHTMJPKixsNCTwJ+swLz1HzPjAPOn8J5g+ehx9szx04g ++SOjO0hYxiuMyEY1RcgQDz8W2eDIY79vQHK6eIdsDX9aDM8TKl0SVkX8yeZVH/l4 +ZrS0GcIlnQKBgHwO78YlX8ydHEM8or7+l3/grJ2lHiMU9LxiDlUA32wJ0owA7qm6 +SiLAMIFypLw+eIPz4rENYoyzGVX5VZhcdKReb37uEoK+xr6F46MSNyfytZYQmxGc +bhDU8+DcvKE0dhHgKVFc6XgliFOUk7IfCc/DUvHG3jSZuGsvGiKjuVtRAoGAL5DF +3P+9UvHiEP+e6WoqH8UvVls3qDO8UNLnfR39Blovh0h5URUUJTAblT+9hLPJloON +GAIlcwHLA86kRlHCPKsFwePZkcbK24mUr+YUONGUUn6wWJOW02uBbYzOAPtKqRR7 +/SK49HM+kH6cAKQREiCnykSTf6Ys7JIEihXWxkECgYEAo/PjT9L+QuYn9muv4p50 +UMJLGQ6kE+63n2dvcEUhXRtEx/i4KzgTkdJ0gBOiaBhJYjzI4BZt/DgtSrzqNC1Y +UUk5i9sJJF5Ypw0n3dX1mYEFlsJ1xFKDU+lwMakdY+X6b+VECKGIQZsh5DOrFRrv +QzRzF4x09HztPUADv4gCL40= +-----END PRIVATE KEY----- diff --git a/cpp/tests/vereign/grpc/identity_api_test.cc b/cpp/tests/vereign/grpc/identity_api_test.cc index 516aa71338dbf10660e61d48b0451f55cb647fc6..2593b8f0781a19ee0886945e5ec15d8f1e23aca5 100644 --- a/cpp/tests/vereign/grpc/identity_api_test.cc +++ b/cpp/tests/vereign/grpc/identity_api_test.cc @@ -15,8 +15,8 @@ # include <vereign/ncrypt/rsa.hh> #endif -#include <util/env.hh> -#include <util/protobuf.hh> +#include <testutil/env.hh> +#include <testutil/protobuf.hh> #include <grpcpp/create_channel.h> #include <catch2/catch.hpp> @@ -24,8 +24,8 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.integration]") { #ifdef _WIN32 SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") { - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); auto storage_path = vereign::fs::TempDir("test_db_"); auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); @@ -64,9 +64,9 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][. } SECTION("when the device key has been changed, it must fail with InvalidIdentity error") { - auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); auto storage_path = vereign::fs::TempDir("test_db_"); auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); @@ -113,8 +113,8 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][. } #else SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") { - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); auto storage_path = vereign::fs::TempDir("test_db_"); auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); @@ -145,9 +145,9 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][. } SECTION("when the pin is wrong, it must fail with InvalidPinCode error") { - auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); const std::string pin = "foo"; auto storage_path = vereign::fs::TempDir("test_db_"); @@ -180,9 +180,9 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][. } SECTION("when the pin is empty, it must fail with InvalidPinCode error") { - auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); const std::string pin = "foo"; auto storage_path = vereign::fs::TempDir("test_db_"); @@ -217,9 +217,9 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][. #endif SECTION("when the device is not authorized, it must fail with DeviceNotRegistered error") { - auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); // the old device is used later for new device confirmation and authorization auto old_storage_path = vereign::fs::TempFilePath("test_db_"); @@ -303,8 +303,8 @@ TEST_CASE("grpc::IdentityAPI::LoginWithNewDevice", "[vereign/grpc][.integration] #ifdef _WIN32 #else SECTION("when the pin is empty, it must fail with InvalidPinCode error") { - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); auto storage_path = vereign::fs::TempDir("test_db_"); auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); diff --git a/cpp/tests/vereign/grpc/server_test.cc b/cpp/tests/vereign/grpc/server_test.cc index 0f3a18589adfb98ebe6d39bb064c91257439b7f5..de3c47db053a0494fbaeb059d1f3535fc0bdcaee 100644 --- a/cpp/tests/vereign/grpc/server_test.cc +++ b/cpp/tests/vereign/grpc/server_test.cc @@ -10,16 +10,16 @@ #include <vereign/test/device.hh> #include <vereign/test/service_context.hh> -#include <util/env.hh> -#include <util/protobuf.hh> +#include <testutil/env.hh> +#include <testutil/protobuf.hh> #include <grpcpp/create_channel.h> #include <catch2/catch.hpp> TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") { - auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); const std::string pin = "foo"; auto storage_path = vereign::fs::TempDir("test_db_"); @@ -84,6 +84,6 @@ TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") { // std::cout << vereign::test::ProtobufToJson(getDIDsResp) << std::endl; - grpc_shutdown(); - google::protobuf::ShutdownProtobufLibrary(); + // grpc_shutdown(); + // google::protobuf::ShutdownProtobufLibrary(); } diff --git a/cpp/tests/vereign/identity/provider_test.cc b/cpp/tests/vereign/identity/provider_test.cc index f34a8450b557e96bbf996aca0e2e43bf6d1481a4..c4de48c2568c74315d2894410f4ff301b939d207 100644 --- a/cpp/tests/vereign/identity/provider_test.cc +++ b/cpp/tests/vereign/identity/provider_test.cc @@ -5,15 +5,24 @@ #include <vereign/kvstore/crypto_storage.hh> #include <vereign/crypto/rand.hh> #include <vereign/bytes/view_dump.hh> +#include <vereign/test/device.hh> +#include <vereign/test/service_context.hh> +#include <vereign/service/gen/passport_service.hh> +#include <vereign/crypto/cert.hh> +#include <vereign/crypto/rsa.hh> +#include <testutil/env.hh> +#include <testutil/protobuf.hh> #include <catch2/catch.hpp> #include <iostream> using namespace vereign; -TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") { +TEST_CASE("identity::Provider::RecreateIdentity", "[vereign/identity]") { auto storage_path = fs::TempFilePath("test_db_"); - fs::RemoveFileGuard rm{storage_path}; + auto rm_storage_path = fs::RemoveFileGuard{storage_path}; + + auto device_ctx = test::ServiceContext{"", "", storage_path}; std::string expected; std::string actual; @@ -22,7 +31,7 @@ TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") { kvstore::SqliteStorage kvstorage{storage_path}; kvstore::CryptoStorage storage{kvstorage, true}; - identity::Provider provider{storage}; + identity::Provider provider{device_ctx.ClientSession(), storage}; expected = provider.RecreateIdentity("foo"); } @@ -30,7 +39,7 @@ TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") { kvstore::SqliteStorage kvstorage{storage_path}; kvstore::CryptoStorage storage{kvstorage, true}; - identity::Provider provider{storage}; + identity::Provider provider{device_ctx.ClientSession(), storage}; actual = provider.LoadIdentity("foo"); } @@ -38,3 +47,169 @@ TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") { CHECK(actual.size() > 0); CHECK(expected == actual); } + +TEST_CASE("identity::Provider::GetProfileCertificate", "[vereign/identity][.integration]") { + auto public_key = testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + // prepare new device + auto old_storage_path = fs::TempFilePath("test_db_"); + auto rm_old_storage_path = fs::RemoveFileGuard{old_storage_path}; + auto old_device_ctx = test::ServiceContext{host, port, old_storage_path}; + auto old_device = test::Device{old_device_ctx}; + old_device.Login(public_key); + + auto storage_path = fs::TempFilePath("test_db_"); + auto rm_storage_path = fs::RemoveFileGuard{storage_path}; + auto service_context = test::ServiceContext{host, port, storage_path}; + old_device.CreateNewDevice(service_context, "pin"); + + // retrieve a profile uuid + auto list_req = std::make_unique<vereign::client_library::ListPassportsForm>(); + auto list_resp = std::make_unique<vereign::client_library::ListPassportsFormResponse>(); + + auto passport_service = service::gen::PassportService{service_context.ClientSession()}; + auto list_result = passport_service.ListPassports(list_req.get(), list_resp.get()); + + auto result = list_result.get(); + auto& list = result.Response; + REQUIRE(list->code() == "200"); + REQUIRE(list->data().size() > 0); + + auto profileUUID = list->data().at(0).uuid(); + + auto& identity_provider = service_context.IdentityProvider(); + + auto cert = identity_provider.GetProfileCertificate(profileUUID); + + REQUIRE(cert.get() != nullptr); + CHECK(cert->PrivateKeyPEM.Size() > 0); + CHECK(cert->CertificatePEM.Size() > 0); + CHECK(cert->CertificateUUID.size() > 0); + CHECK(cert->Chain.size() == 2); + + SECTION("the certificate private key must be importable") { + bssl::UniquePtr<EVP_PKEY> cert_pkey; + CHECK_NOTHROW( + cert_pkey = crypto::rsa::ImportPrivateKeyFromPEM(cert->PrivateKeyPEM.View()) + ); + CHECK(cert_pkey.get() != nullptr); + } + + SECTION("the certificate and the certificates from the chain must be importable") { + bssl::UniquePtr<X509> x509_cert; + CHECK_NOTHROW( + x509_cert = crypto::cert::ImportCertFromPEM(cert->CertificatePEM.View()) + ); + CHECK(x509_cert.get() != nullptr); + + for (auto& cert_pem : cert->Chain) { + x509_cert.reset(); + + CHECK_NOTHROW( + x509_cert = crypto::cert::ImportCertFromPEM(cert_pem.View()) + ); + CHECK(x509_cert.get() != nullptr); + } + } + + + SECTION("it must reuse already created profile certificates") { + auto new_cert = identity_provider.GetProfileCertificate(profileUUID); + + REQUIRE(new_cert.get() != nullptr); + CHECK(new_cert->PrivateKeyPEM.View() == cert->PrivateKeyPEM.View()); + CHECK(new_cert->CertificatePEM.View() == cert->CertificatePEM.View()); + CHECK(new_cert->CertificateUUID == cert->CertificateUUID); + REQUIRE(new_cert->Chain.size() == cert->Chain.size()); + for (std::size_t i = 0; i < new_cert->Chain.size(); i++) { + CHECK(new_cert->Chain[i].View() == cert->Chain[i].View()); + } + } +} + +TEST_CASE("identity::Provider::GetProfileOneTimeCertificate", "[vereign/identity][.integration]") { + auto public_key = testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + // prepare new device + auto old_storage_path = fs::TempFilePath("test_db_"); + auto rm_old_storage_path = fs::RemoveFileGuard{old_storage_path}; + auto old_device_ctx = test::ServiceContext{host, port, old_storage_path}; + auto old_device = test::Device{old_device_ctx}; + old_device.Login(public_key); + + auto storage_path = fs::TempFilePath("test_db_"); + auto rm_storage_path = fs::RemoveFileGuard{storage_path}; + auto service_context = test::ServiceContext{host, port, storage_path}; + old_device.CreateNewDevice(service_context, "pin"); + + // retrieve a profile uuid + auto list_req = std::make_unique<vereign::client_library::ListPassportsForm>(); + auto list_resp = std::make_unique<vereign::client_library::ListPassportsFormResponse>(); + + auto passport_service = service::gen::PassportService{service_context.ClientSession()}; + auto list_result = passport_service.ListPassports(list_req.get(), list_resp.get()); + + auto result = list_result.get(); + auto& list = result.Response; + REQUIRE(list->code() == "200"); + REQUIRE(list->data().size() > 0); + + auto profileUUID = list->data().at(0).uuid(); + + auto& identity_provider = service_context.IdentityProvider(); + + auto cert = identity_provider.GetProfileOneTimeCertificate(profileUUID, "foo@example.com"); + + REQUIRE(cert.get() != nullptr); + CHECK(cert->PrivateKeyPEM.Size() > 0); + CHECK(cert->CertificatePEM.Size() > 0); + CHECK(cert->CertificateUUID.size() == 0); + CHECK(cert->Chain.size() == 3); + + SECTION("the certificate private key must be importable") { + bssl::UniquePtr<EVP_PKEY> cert_pkey; + CHECK_NOTHROW( + cert_pkey = crypto::rsa::ImportPrivateKeyFromPEM(cert->PrivateKeyPEM.View()) + ); + CHECK(cert_pkey.get() != nullptr); + } + + // fs::WriteFile("onetime", cert->CertificatePEM.View()); + // fs::WriteFile("userdevice", cert->Chain[0].View()); + // fs::WriteFile("serverside", cert->Chain[1].View()); + // fs::WriteFile("vereign", cert->Chain[2].View()); + + SECTION("the certificate and the certificates from the chain must be importable") { + bssl::UniquePtr<X509> x509_cert; + CHECK_NOTHROW( + x509_cert = crypto::cert::ImportCertFromPEM(cert->CertificatePEM.View()) + ); + CHECK(x509_cert.get() != nullptr); + + for (auto& cert_pem : cert->Chain) { + x509_cert.reset(); + + CHECK_NOTHROW( + x509_cert = crypto::cert::ImportCertFromPEM(cert_pem.View()) + ); + CHECK(x509_cert.get() != nullptr); + } + } + + SECTION("it must create new certificate all the time, but the issuers chain must be the same") { + auto new_cert = identity_provider.GetProfileOneTimeCertificate(profileUUID, "bar@example.com"); + + REQUIRE(new_cert.get() != nullptr); + CHECK(new_cert->PrivateKeyPEM.View() != cert->PrivateKeyPEM.View()); + CHECK(new_cert->CertificatePEM.View() != cert->CertificatePEM.View()); + CHECK(new_cert->CertificateUUID.size() == 0); + REQUIRE(new_cert->Chain.size() == cert->Chain.size()); + for (std::size_t i = 0; i < new_cert->Chain.size(); i++) { + CHECK(new_cert->Chain[i].View() == cert->Chain[i].View()); + } + } +} diff --git a/cpp/tests/vereign/kvstore/crypto_storage_test.cc b/cpp/tests/vereign/kvstore/crypto_storage_test.cc index 8f9d305d0488004739afe3ae142e982ab7405630..01524db2f8dc171002b49e71addfa33289b20737 100644 --- a/cpp/tests/vereign/kvstore/crypto_storage_test.cc +++ b/cpp/tests/vereign/kvstore/crypto_storage_test.cc @@ -70,10 +70,10 @@ TEST_CASE("kvstore::CryptoStorage::GetBytes", "[vereign/kvstore]") { storage.Recreate("foo"); - SECTION("when the value does not exists, it must throw ValueNotFoundError") { + SECTION("when the value does not exists, it must return false") { bytes::Buffer v; - CHECK_THROWS_AS(storage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError); + CHECK(storage.GetBytes("does_not_exists", v) == false); CHECK(v.Size() == 0); } diff --git a/cpp/tests/vereign/kvstore/lock_test.cc b/cpp/tests/vereign/kvstore/lock_test.cc index d15ed5ef9da02727c382e713c2401e2083ce7b5d..7dd70ed7943d1ec7400c2e3db7534875d979a6a0 100644 --- a/cpp/tests/vereign/kvstore/lock_test.cc +++ b/cpp/tests/vereign/kvstore/lock_test.cc @@ -8,7 +8,7 @@ #include <vereign/core/scope_guard.hh> #include <vereign/crypto/rand.hh> #include <vereign/sqlite/errors.hh> -#include <util/error.hh> +#include <testutil/error.hh> #include <sqlite3.h> #include <catch2/catch.hpp> diff --git a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc index f747ef2e4225b2b4c38b68551ab5cec2fcb538ae..dfe2d3d117038493c6338a3e202b8a1a2e8eec99 100644 --- a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc +++ b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc @@ -7,7 +7,7 @@ #include <vereign/core/scope_guard.hh> #include <vereign/crypto/rand.hh> #include <vereign/sqlite/errors.hh> -#include <util/error.hh> +#include <testutil/error.hh> #include <sqlite3.h> #include <catch2/catch.hpp> @@ -23,11 +23,12 @@ TEST_CASE("kvstore::SqliteStorage::GetBytes", "[vereign/kvstore]") { auto kvstorage = kvstore::SqliteStorage(storage_path); - SECTION("when the value does not exists, it must throw ValueNotFoundError") { + SECTION("when the value does not exists, it must return false") { bytes::Buffer v; - CHECK_THROWS_AS(kvstorage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError); + auto found = kvstorage.GetBytes("does_not_exists", v); + CHECK(found == false); CHECK(v.Size() == 0); } @@ -35,8 +36,9 @@ TEST_CASE("kvstore::SqliteStorage::GetBytes", "[vereign/kvstore]") { kvstorage.PutBytes("foo", bytes::View("bar")); bytes::Buffer v; - kvstorage.GetBytes("foo", v); + auto found = kvstorage.GetBytes("foo", v); + CHECK(found == true); CHECK(v.View() == bytes::View("bar")); } } @@ -47,15 +49,21 @@ TEST_CASE("kvstore::SqliteStorage::GetInt64", "[vereign/kvstore]") { auto kvstorage = kvstore::SqliteStorage(storage_path); - SECTION("when the value does not exists, it must throw ValueNotFoundError") { - CHECK_THROWS_AS(kvstorage.GetInt64("does_not_exists"), kvstore::ValueNotFoundError); + SECTION("when the value does not exists, it must return false") { + int64_t value = 0; + auto found = kvstorage.GetInt64("does_not_exists", value); + + CHECK(found == false); + CHECK(value == 0); } SECTION("when the value exists, it must retrieve the value") { kvstorage.PutInt64("foo", 42); - auto v = kvstorage.GetInt64("foo"); + int64_t v = 0; + auto result = kvstorage.GetInt64("foo", v); + CHECK(result == true); CHECK(v == 42); } } @@ -69,13 +77,16 @@ TEST_CASE("kvstore::SqliteStorage::DeleteAll", "[vereign/kvstore]") { kvstorage.PutInt64("foo", 42); kvstorage.PutInt64("bar", 422); - CHECK(kvstorage.GetInt64("foo") == 42); - CHECK(kvstorage.GetInt64("bar") == 422); + int64_t v = 0; + kvstorage.GetInt64("foo", v); + CHECK(v == 42); + kvstorage.GetInt64("bar", v); + CHECK(v == 422); kvstorage.DeleteAll(); - CHECK_THROWS_AS(kvstorage.GetInt64("foo"), kvstore::ValueNotFoundError); - CHECK_THROWS_AS(kvstorage.GetInt64("bar"), kvstore::ValueNotFoundError); + CHECK(kvstorage.GetInt64("foo", v) == false); + CHECK(kvstorage.GetInt64("bar", v) == false); } TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") { @@ -98,8 +109,12 @@ TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") { kvstore::Lock l{kvstorage}; - CHECK(kvstorage.GetInt64("foo") == 42); - CHECK(kvstorage.GetInt64("bar") == 422); + int64_t v = 0; + kvstorage.GetInt64("foo", v); + CHECK(v == 42); + + kvstorage.GetInt64("bar", v); + CHECK(v == 422); } } @@ -119,8 +134,12 @@ TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") { auto kvstorage = kvstore::SqliteStorage(storage_path); kvstorage.Lock(); - CHECK(kvstorage.GetInt64("foo") == 42); - CHECK(kvstorage.GetInt64("bar") == 422); + int64_t v = 0; + kvstorage.GetInt64("foo", v); + CHECK(v == 42); + + kvstorage.GetInt64("bar", v); + CHECK(v == 422); } } diff --git a/cpp/tests/vereign/restapi/client_session_test.cc b/cpp/tests/vereign/restapi/client_session_test.cc index 36a824999a5c725e0d5443d91c91369fac619c51..88b361497a33b2c64e7e51677b12faa1fcf537b5 100644 --- a/cpp/tests/vereign/restapi/client_session_test.cc +++ b/cpp/tests/vereign/restapi/client_session_test.cc @@ -2,8 +2,8 @@ #include <vereign/restapi/client_session.hh> #include <vereign/core/scope_guard.hh> #include <vereign/client_library/types.gen.pb.h> -#include <util/env.hh> -#include <util/protobuf.hh> +#include <testutil/env.hh> +#include <testutil/protobuf.hh> #include <catch2/catch.hpp> @@ -11,22 +11,20 @@ #include <boost/asio/ssl/context.hpp> #include <fmt/core.h> -TEST_CASE("ClientSession::Post", "[vereign/restapi/client_session][.integration]") { +TEST_CASE("restapi::ClientSession::Post", "[vereign/restapi/client_session][.integration]") { namespace asio = boost::asio; namespace beast = boost::beast; asio::io_context ioc; auto work_guard = boost::asio::make_work_guard(ioc); asio::ssl::context ctx(asio::ssl::context::tlsv12_client); - auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); - - // Verify the remote server's certificate - // ctx.set_verify_mode(ssl::verify_peer); + auto publicKey = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); vereign::restapi::Client client{ioc, ctx, host, port}; - vereign::restapi::ClientSession client_session{client, publicKey}; + vereign::restapi::ClientSession client_session{client}; + client_session.SetPubKey(publicKey); std::thread ioc_thread([&ioc]{ ioc.run(); diff --git a/cpp/tests/vereign/restapi/client_test.cc b/cpp/tests/vereign/restapi/client_test.cc index c0c1a17198abca5ee1e50f85fe57675308d4d597..27b71a716c6d4a7c22e3e8fbef88f8ae4dd83147 100644 --- a/cpp/tests/vereign/restapi/client_test.cc +++ b/cpp/tests/vereign/restapi/client_test.cc @@ -4,7 +4,7 @@ #include <vereign/client_library/identity_types.pb.h> #include <vereign/client_library/passport_api.gen.pb.h> #include <vereign/core/scope_guard.hh> -#include <util/env.hh> +#include <testutil/env.hh> #include "boost/asio/executor_work_guard.hpp" #include "boost/beast/core/error.hpp" @@ -25,9 +25,9 @@ TEST_CASE("Client::Post", "[vereign/restapi/client][.integration]") { auto work_guard = boost::asio::make_work_guard(ioc); asio::ssl::context ctx(asio::ssl::context::tlsv12_client); - auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto publicKey = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); // Verify the remote server's certificate // ctx.set_verify_mode(ssl::verify_peer); @@ -102,9 +102,9 @@ TEST_CASE("Client load test", "[vereign/restapi/client][.bench]") { auto work_guard = boost::asio::make_work_guard(ioc); asio::ssl::context ctx(asio::ssl::context::tlsv12_client); - auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto publicKey = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); host = "localhost"; port = "9292"; diff --git a/cpp/tests/vereign/service/gen/passport_service_test.cc b/cpp/tests/vereign/service/gen/passport_service_test.cc index bbe2df7c0aaf5bb31dad33039778e439b6606ce6..2953fe0e466c60bba2797104b7e556b970d21bf4 100644 --- a/cpp/tests/vereign/service/gen/passport_service_test.cc +++ b/cpp/tests/vereign/service/gen/passport_service_test.cc @@ -8,8 +8,8 @@ #include <vereign/service/identity_service.hh> #include <vereign/identity/provider.hh> -#include <util/env.hh> -#include <util/protobuf.hh> +#include <testutil/env.hh> +#include <testutil/protobuf.hh> #include <catch2/catch.hpp> #include <boost/asio/io_context.hpp> @@ -23,9 +23,9 @@ TEST_CASE("PassportService::ListPassports", "[vereign/service/gen][.integration] auto work_guard = boost::asio::make_work_guard(ioc); asio::ssl::context ctx(asio::ssl::context::tlsv12_client); - auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto publicKey = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); // Verify the remote server's certificate // ctx.set_verify_mode(ssl::verify_peer); @@ -39,7 +39,7 @@ TEST_CASE("PassportService::ListPassports", "[vereign/service/gen][.integration] auto kvstorage = vereign::kvstore::SqliteStorage(storage_path); vereign::kvstore::CryptoStorage storage{kvstorage}; - vereign::identity::Provider provider{storage}; + vereign::identity::Provider provider{client_session, storage}; vereign::service::IdentityService idenity_service{client_session, provider}; std::thread ioc_thread([&ioc]{ diff --git a/cpp/tests/vereign/service/identity_service_test.cc b/cpp/tests/vereign/service/identity_service_test.cc index 4fa38954ab5c0ec1af69addc32ffccd0a23c8e5a..4f48b025e33140f7a77bd8897ed393f7476c5ed3 100644 --- a/cpp/tests/vereign/service/identity_service_test.cc +++ b/cpp/tests/vereign/service/identity_service_test.cc @@ -10,17 +10,17 @@ #include <vereign/test/service_context.hh> #include <vereign/fs/util.hh> -#include <util/env.hh> -#include <util/protobuf.hh> +#include <testutil/env.hh> +#include <testutil/protobuf.hh> #include <catch2/catch.hpp> using namespace vereign; TEST_CASE("service::IdentityService::LoginWithNewDevice", "[vereign/service][.integration]") { - auto public_key = test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto public_key = testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); // the old device is used later for new device confirmation and authorization auto old_storage_path = fs::TempFilePath("test_db_"); @@ -75,9 +75,9 @@ TEST_CASE("service::IdentityService::LoginWithNewDevice", "[vereign/service][.in } TEST_CASE("service::IdentityService::LoginWithPreviouslyAddedDevice", "[vereign/service][.integration]") { - auto public_key = test::RequireEnv("TEST_VEREIGN_PUB_KEY"); - auto host = test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + auto public_key = testutil::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = testutil::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = testutil::GetEnv("TEST_VEREIGN_API_PORT", "https"); auto storage_path = fs::TempFilePath("test_db_"); auto rm_storage_path = fs::RemoveFileGuard{storage_path}; diff --git a/cpp/tests/vereign/test/service_context.cc b/cpp/tests/vereign/test/service_context.cc index a6b5fc85a675920a667cd0890ffe0b632cda516a..934ff9084eb440a3c536d4e0f58baaf9c3518b0a 100644 --- a/cpp/tests/vereign/test/service_context.cc +++ b/cpp/tests/vereign/test/service_context.cc @@ -23,7 +23,7 @@ ServiceContext::ServiceContext( storage_path_{std::move(storage_path)}, sqlite_storage_{std::make_unique<kvstore::SqliteStorage>(storage_path_)}, storage_{std::make_unique<kvstore::CryptoStorage>(*sqlite_storage_, true)}, - identity_provider_{std::make_unique<identity::Provider>(*storage_)} + identity_provider_{std::make_unique<identity::Provider>(*client_session_, *storage_)} { service_thread_ = std::thread([this]() { ioc_.run();