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