Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
Providers/OpenSSL/Certificate.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#if qStroika_HasComponent_OpenSSL
7#include <openssl/evp.h>
8#include <openssl/pem.h>
9#endif
10
13#include "Stroika/Foundation/Cryptography/Providers/OpenSSL/PrivateKey.h"
16#include "Stroika/Foundation/Execution/Exceptions.h"
19
20#include "Certificate.h"
21
22using namespace Stroika::Foundation;
25using namespace Stroika::Foundation::Cryptography;
26using namespace Stroika::Foundation::Cryptography::PKI::Certificate;
27using namespace Stroika::Foundation::Cryptography::Providers;
28using namespace Stroika::Foundation::Cryptography::Providers::OpenSSL;
29using namespace Stroika::Foundation::Debug;
30
31using Memory::MakeSharedPtr;
32
33// Comment this in to turn on aggressive noisy DbgTrace in this module
34// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
35
36#if qStroika_HasComponent_OpenSSL
37namespace {
38
39 String asn1ToString_ (const ASN1_STRING* asn1_str)
40 {
41 unsigned char* utf8_out = NULL;
42 int length = ::ASN1_STRING_to_UTF8 (&utf8_out, asn1_str);
43 if (length < 0) {
44 return String{};
45 }
46 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { ::OPENSSL_free (utf8_out); });
47 return String::FromUTF8 ((const char*)utf8_out);
48 }
49
50 struct Rep_ : OpenSSL::Certificate::IRep {
51
52 OpenSSL::Certificate::LibRepType fCert_;
53
54 Rep_ () = delete;
55 Rep_ (const Rep_&) = delete;
56 Rep_ (Rep_&&) = default;
57 Rep_ (OpenSSL::Certificate::LibRepType&& p)
58 : fCert_{move (p)}
59 {
60 }
61 virtual Range<DateTime> GetValidDates () const override
62 {
63 using Time::Timezone;
64 struct tm from{};
65 struct tm to{};
66 Exception::ThrowLastErrorIfFailed (::ASN1_TIME_to_tm (X509_get_notBefore (fCert_.get ()), &from));
67 Exception::ThrowLastErrorIfFailed (::ASN1_TIME_to_tm (X509_get_notAfter (fCert_.get ()), &to));
68 return Range<DateTime>{DateTime{from, Timezone::kUTC}, DateTime{to, Timezone::kUTC}};
69 }
70 virtual SubjectInfo GetSubject () const override
71 {
72 SubjectInfo result;
73 const X509_NAME* subject = ::X509_get_subject_name (fCert_.get ());
74 int numEntries = ::X509_NAME_entry_count (subject);
75 for (int i = 0; i < numEntries; ++i) {
76 const X509_NAME_ENTRY* entry = ::X509_NAME_get_entry (subject, i);
77 const ASN1_OBJECT* nid = ::X509_NAME_ENTRY_get_object (entry);
78 if (::OBJ_cmp (nid, ::OBJ_nid2obj (NID_commonName)) == 0) {
79 result.fCommonName = asn1ToString_ (::X509_NAME_ENTRY_get_data (entry));
80 }
81 else if (::OBJ_cmp (nid, ::OBJ_nid2obj (NID_countryName)) == 0) {
82 result.fCountry = asn1ToString_ (::X509_NAME_ENTRY_get_data (entry));
83 }
84 else if (::OBJ_cmp (nid, ::OBJ_nid2obj (NID_organizationName)) == 0) {
85 result.fOrganization = asn1ToString_ (::X509_NAME_ENTRY_get_data (entry));
86 }
87 }
88 return result;
89 }
90 virtual X509* Get_X509 () const override
91 {
92 return fCert_.get ();
93 }
94 };
95}
96#endif
97
98#if qStroika_HasComponent_OpenSSL
99/*
100 ********************************************************************************
101 ****************************** OpenSSL::Certificate ****************************
102 ********************************************************************************
103 */
104auto OpenSSL::Certificate::New (LibRepType&& x509) -> Ptr
105{
106 return MakeSharedPtr<Rep_> (move (x509));
107}
108
109auto OpenSSL::Certificate::New (const SelfSignedCertParams& params) -> tuple<OpenSSL::PrivateKey::Ptr, Ptr>
110{
111 // Code adapted from https://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl
112 PrivateKey::LibRepType pkey{::EVP_RSA_gen (2048)};
113
114 LibRepType newCert{X509_new ()};
115
116 Exception::ThrowLastErrorIfFailed (::ASN1_INTEGER_set (::X509_get_serialNumber (newCert.get ()), 1));
117
118 ::ASN1_TIME_set (::X509_get_notBefore (newCert.get ()), params.fValidDates.GetLowerBound ().AsUTC ().As<time_t> ());
119 ::ASN1_TIME_set (::X509_get_notAfter (newCert.get ()), params.fValidDates.GetUpperBound ().AsUTC ().As<time_t> ());
120
121 // Set public key to be the key we generated earlier
122 Exception::ThrowLastErrorIfFailed (::X509_set_pubkey (newCert.get (), pkey.get ()));
123
124 u8string org = params.fSubject.fOrganization.AsUTF8 ();
125 u8string cn = params.fSubject.fCommonName.AsUTF8 ();
126 u8string country = params.fSubject.fCountry.AsUTF8 ();
127 X509_NAME* name = X509_NAME_new ();
128 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { ::X509_NAME_free (name); });
129 Exception::ThrowLastErrorIfFailed (
130 ::X509_NAME_add_entry_by_txt (name, "C", MBSTRING_UTF8, reinterpret_cast<const unsigned char*> (country.c_str ()), -1, -1, 0));
131 Exception::ThrowLastErrorIfFailed (
132 ::X509_NAME_add_entry_by_txt (name, "O", MBSTRING_UTF8, reinterpret_cast<const unsigned char*> (org.c_str ()), -1, -1, 0));
133 Exception::ThrowLastErrorIfFailed (
134 ::X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_UTF8, reinterpret_cast<const unsigned char*> (cn.c_str ()), -1, -1, 0));
135 Exception::ThrowLastErrorIfFailed (::X509_set_subject_name (newCert.get (), name));
136 // Since this is a self-signed certificate, we set the name of the issuer to the name of the subject
137 Exception::ThrowLastErrorIfFailed (::X509_set_issuer_name (newCert.get (), name));
138
139 // Now sign with SHA1 digest
140 Exception::ThrowLastErrorIfFailed (::X509_sign (newCert.get (), pkey.get (), ::EVP_sha1 ()));
141 return make_tuple (OpenSSL::PrivateKey::New (move (pkey)), New (move (newCert)));
142}
143#endif
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201