Stroika Library 3.0d23x
 
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"
18
19#include "Certificate.h"
20
21using namespace Stroika::Foundation;
24using namespace Stroika::Foundation::Cryptography;
25using namespace Stroika::Foundation::Cryptography::PKI::Certificate;
26using namespace Stroika::Foundation::Cryptography::Providers;
27using namespace Stroika::Foundation::Cryptography::Providers::OpenSSL;
28using namespace Stroika::Foundation::Debug;
29
30using Memory::MakeSharedPtr;
31
32// Comment this in to turn on aggressive noisy DbgTrace in this module
33// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
34
35#if qStroika_HasComponent_OpenSSL
36namespace {
37 struct Rep_ : OpenSSL::Certificate::IRep {
38
39 OpenSSL::Certificate::LibRepType fCert_;
40
41 Rep_ () = delete;
42 Rep_ (const Rep_&) = delete;
43 Rep_ (Rep_&&) = default;
44 Rep_ (OpenSSL::Certificate::LibRepType&& p)
45 : fCert_{move (p)}
46 {
47 }
48 virtual Range<DateTime> GetValidDates () const override
49 {
50 using Time::Timezone;
51 struct tm from{};
52 struct tm to{};
53 Exception::ThrowLastErrorIfFailed (::ASN1_TIME_to_tm (X509_get_notBefore (fCert_.get ()), &from));
54 Exception::ThrowLastErrorIfFailed (::ASN1_TIME_to_tm (X509_get_notAfter (fCert_.get ()), &to));
55 return Range<DateTime>{DateTime{from, Timezone::kUTC}, DateTime{to, Timezone::kUTC}};
56 }
57 virtual SubjectInfo GetSubject () const override
58 {
59 SubjectInfo result;
60 X509_NAME* subject = ::X509_get_subject_name (fCert_.get ());
61 int numEntries = ::X509_NAME_entry_count (subject);
62 for (int i = 0; i < numEntries; ++i) {
63 X509_NAME_ENTRY* entry = ::X509_NAME_get_entry (subject, i);
64 ASN1_OBJECT* nid = ::X509_NAME_ENTRY_get_object (entry);
65 if (::OBJ_cmp (nid, ::OBJ_nid2obj (NID_commonName)) == 0) {
66 unsigned char* cn = X509_NAME_ENTRY_get_data (entry)->data;
67 result.fCommonName = String::FromUTF8 ((const char*)cn);
68 }
69 else if (::OBJ_cmp (nid, ::OBJ_nid2obj (NID_countryName)) == 0) {
70 unsigned char* cn = ::X509_NAME_ENTRY_get_data (entry)->data;
71 result.fCountry = String::FromUTF8 ((const char*)cn);
72 }
73 else if (::OBJ_cmp (nid, ::OBJ_nid2obj (NID_organizationName)) == 0) {
74 unsigned char* cn = ::X509_NAME_ENTRY_get_data (entry)->data;
75 result.fOrganization = String::FromUTF8 ((const char*)cn);
76 }
77 }
78 return result;
79 }
80 virtual X509* Get_X509 () const override
81 {
82 return fCert_.get ();
83 }
84 };
85}
86#endif
87
88#if qStroika_HasComponent_OpenSSL
89/*
90 ********************************************************************************
91 ****************************** OpenSSL::Certificate ****************************
92 ********************************************************************************
93 */
94auto OpenSSL::Certificate::New (LibRepType&& x509) -> Ptr
95{
96 return MakeSharedPtr<Rep_> (move (x509));
97}
98
99auto OpenSSL::Certificate::New (const SelfSignedCertParams& params) -> tuple<OpenSSL::PrivateKey::Ptr, Ptr>
100{
101 // Code adapted from https://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl
102 PrivateKey::LibRepType pkey{::EVP_RSA_gen (2048)};
103
104 LibRepType newCert{X509_new ()};
105
106 Exception::ThrowLastErrorIfFailed (::ASN1_INTEGER_set (::X509_get_serialNumber (newCert.get ()), 1));
107
108 ::ASN1_TIME_set (::X509_get_notBefore (newCert.get ()), params.fValidDates.GetLowerBound ().AsUTC ().As<time_t> ());
109 ::ASN1_TIME_set (::X509_get_notAfter (newCert.get ()), params.fValidDates.GetUpperBound ().AsUTC ().As<time_t> ());
110
111 // Set public key to be the key we generated earlier
112 Exception::ThrowLastErrorIfFailed (::X509_set_pubkey (newCert.get (), pkey.get ()));
113
114 X509_NAME* name = ::X509_get_subject_name (newCert.get ());
115 u8string org = params.fSubject.fOrganization.AsUTF8 ();
116 u8string cn = params.fSubject.fCommonName.AsUTF8 ();
117 u8string country = params.fSubject.fCountry.AsUTF8 ();
118
119 Exception::ThrowLastErrorIfFailed (::X509_NAME_add_entry_by_txt (name, "C", MBSTRING_UTF8, (unsigned char*)country.c_str (), -1, -1, 0));
120 Exception::ThrowLastErrorIfFailed (::X509_NAME_add_entry_by_txt (name, "O", MBSTRING_UTF8, (unsigned char*)org.c_str (), -1, -1, 0));
121 Exception::ThrowLastErrorIfFailed (::X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_UTF8, (unsigned char*)cn.c_str (), -1, -1, 0));
122
123 // Since this is a self-signed certificate, we set the name of the issuer to the name of the subject
124 Exception::ThrowLastErrorIfFailed (::X509_set_issuer_name (newCert.get (), name));
125
126 // Now sign with SHA1 digest
127 Exception::ThrowLastErrorIfFailed (::X509_sign (newCert.get (), pkey.get (), ::EVP_sha1 ()));
128 return make_tuple (OpenSSL::PrivateKey::New (move (pkey)), New (move (newCert)));
129}
130#endif