OpenCPN Partial API docs
Loading...
Searching...
No Matches
certificates.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2022 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include "config.h"
25
26#include <cstdio>
27#include <iostream>
28#include <string.h>
29
30#include <openssl/err.h>
31#include <openssl/evp.h>
32#include <openssl/pem.h>
33#include <openssl/x509.h>
34#include <openssl/x509v3.h>
35
36#ifdef __MSVC__
37#include "openssl/applink.c"
38#endif
39
40#ifdef HAVE_OCPN_LOG
41#include "model/logger.h"
42#define cerr ERROR_LOG
43#endif
44
45using namespace std;
46
47/* Generates a 2048-bit RSA key. */
48EVP_PKEY *generate_key() {
49 /* Allocate memory for the EVP_PKEY structure. */
50 EVP_PKEY *pkey = EVP_PKEY_new();
51 if (!pkey) {
52 cerr << "Unable to create EVP_PKEY structure." << endl;
53 return NULL;
54 }
55
56 /* Generate the RSA key and assign it to pkey. */
57 RSA *rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL);
58 if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
59 cerr << "Unable to generate 2048-bit RSA key." << endl;
60 EVP_PKEY_free(pkey);
61 return NULL;
62 }
63
64 /* The key has been generated, return it. */
65 return pkey;
66}
67
68int cs_cert_set_subject_alt_name(X509 *x509_cert, string name) {
69 const char *subject_alt_name = name.c_str(); //"IP: 192.168.1.1";
70 X509_EXTENSION *extension_san = NULL;
71 ASN1_OCTET_STRING *subject_alt_name_ASN1 = NULL;
72 int ret = -1;
73
74 subject_alt_name_ASN1 = ASN1_OCTET_STRING_new();
75 if (!subject_alt_name_ASN1) {
76 goto err;
77 }
78 ASN1_OCTET_STRING_set(subject_alt_name_ASN1,
79 (unsigned char *)subject_alt_name,
80 strlen(subject_alt_name));
81 if (!X509_EXTENSION_create_by_NID(&extension_san, NID_subject_alt_name, 0,
82 subject_alt_name_ASN1)) {
83 goto err;
84 }
85 ASN1_OCTET_STRING_free(subject_alt_name_ASN1);
86 ret = X509_add_ext(x509_cert, extension_san, -1);
87 if (!ret) {
88 goto err;
89 }
90 X509_EXTENSION_free(extension_san);
91 return 0;
92
93err:
94 if (subject_alt_name_ASN1) ASN1_OCTET_STRING_free(subject_alt_name_ASN1);
95 if (extension_san) X509_EXTENSION_free(extension_san);
96 return -1;
97}
98
99/* Generates a self-signed x509 certificate. */
100X509 *generate_x509(EVP_PKEY *pkey, string ip_v4) {
101 /* Allocate memory for the X509 structure. */
102 X509 *x509 = X509_new();
103 if (!x509) {
104 cerr << "Unable to create X509 structure." << endl;
105 return NULL;
106 }
107
108 /* Set the serial number. */
109 ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
110
111 /* This certificate is valid from now until exactly one year from now. */
112
113 X509_gmtime_adj(X509_get_notBefore(x509), 0);
114 X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
115
116 /* Set the public key for our certificate. */
117 X509_set_pubkey(x509, pkey);
118
119 /* We want to copy the subject name to the issuer name. */
120#if OPENSSL_VERSION_MAJOR >= 4
121 const X509_NAME *const_name = X509_get_subject_name(x509);
122 X509_NAME *name = X509_NAME_dup(const_name);
123#else
124 X509_NAME *name = X509_get_subject_name(x509);
125#endif
126
127 /* Set the country code and common name. */
128 X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"CA", -1,
129 -1, 0);
130 X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
131 (unsigned char *)"MyCompany", -1, -1, 0);
132 X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
133 (unsigned char *)"localhost", -1, -1, 0);
134 string ext_name("IP: ");
135 ext_name += ip_v4;
136 cs_cert_set_subject_alt_name(x509, ext_name);
137
138 /* Now set the issuer name. */
139 X509_set_issuer_name(x509, name);
140
141#if OPENSSL_VERSION_MAJOR >= 4
142 /* We are done with name */
143 X509_NAME_free(name);
144#endif
145
146 /* Actually sign the certificate with our key. */
147#if OPENSSL_VERSION_MAJOR * 10 + OPENSSL_VERSION_MINOR >= 32
148 const EVP_MD *evp_md = EVP_MD_fetch(0, "SHA-256", 0);
149#else
150 const EVP_MD *evp_md = EVP_sha1();
151#endif
152 if (!X509_sign(x509, pkey, evp_md)) {
153 unsigned long error = ERR_peek_error();
154 char *errmsg = ERR_error_string(error, 0);
155 cerr << "Error signing certificate:" << errmsg << endl;
156 X509_free(x509);
157 return NULL;
158 }
159
160 return x509;
161}
162
163bool write_to_disk(EVP_PKEY *pkey, X509 *x509, string cert_directory) {
164 /* Open the PEM file for writing the key to disk. */
165 string key_file = cert_directory;
166 key_file += "key.pem";
167 FILE *pkey_file = fopen(key_file.c_str(), "wb");
168 if (!pkey_file) {
169 cerr << "Unable to open \"key.pem\" for writing." << endl;
170 return false;
171 }
172
173 /* Write the key to disk. */
174 bool ret = PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL);
175 fclose(pkey_file);
176
177 if (!ret) {
178 cerr << "Unable to write private key to disk." << endl;
179 return false;
180 }
181
182 /* Open the PEM file for writing the certificate to disk. */
183 string cert_file = cert_directory;
184 cert_file += "cert.pem";
185
186 FILE *x509_file = fopen(cert_file.c_str(), "wb");
187 if (!x509_file) {
188 cerr << "Unable to open \"cert.pem\" for writing." << endl;
189 return false;
190 }
191
192 /* Write the certificate to disk. */
193 ret = PEM_write_X509(x509_file, x509);
194 fclose(x509_file);
195
196 if (!ret) {
197 cerr << "Unable to write certificate to disk." << endl;
198 return false;
199 }
200
201 return true;
202}
203
204int make_certificate(string ipv4, string destination_dir) {
205 /* Generate the key. */
206 if (getenv("OCPN_DEBUG_CERT")) cout << "Generating RSA key..." << endl;
207
208 EVP_PKEY *pkey = generate_key();
209 if (!pkey) return 1;
210
211 /* Generate the certificate. */
212 if (getenv("OCPN_DEBUG_CERT"))
213 cout << "Generating x509 certificate..." << endl;
214
215 X509 *x509 = generate_x509(pkey, ipv4);
216 if (!x509) {
217 EVP_PKEY_free(pkey);
218 return 1;
219 }
220
221 /* Write the private key and certificate out to disk. */
222 if (getenv("OCPN_DEBUG_CERT"))
223 cout << "Writing key and certificate to disk..." << endl;
224
225 bool ret = write_to_disk(pkey, x509, destination_dir);
226 EVP_PKEY_free(pkey);
227 X509_free(x509);
228
229 if (ret) {
230 if (getenv("OCPN_DEBUG_CERT")) cout << "Success!" << endl;
231 return 0;
232 } else
233 return 1;
234}
Enhanced logging interface on top of wx/log.h.