Skip to content
Snippets Groups Projects
Commit a100f75d authored by Gospodin Bodurov's avatar Gospodin Bodurov
Browse files

Merge branch 'profile-certificates' into 'master'

Profile certificates

See merge request !98
parents 41eca2f7 b9311b0b
No related branches found
No related tags found
1 merge request!98Profile certificates
Showing
with 1097 additions and 29 deletions
Subproject commit 3e82fc7d60056e08b041577e332f1ec5029633ee
Subproject commit c581306f8816e5e8b708c9097e402354bb41b84a
......@@ -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
......
#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;
}
#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
#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
#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, &not_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, &not_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
#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
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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);
......
......@@ -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.
*
......
......@@ -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 ;
......
......@@ -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.
*
......
#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"}
{
}
};
......
......@@ -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
#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
......@@ -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);
......
......@@ -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
#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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment