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 <cstdio>
31#include <iostream>
32#include <string.h>
33
34#include <openssl/pem.h>
35#include <openssl/x509.h>
36#include <openssl/x509v3.h>
37
38#ifdef __MSVC__
39#include "openssl/applink.c"
40#endif
41
42
43/* Generates a 2048-bit RSA key. */
44EVP_PKEY * generate_key()
45{
46 /* Allocate memory for the EVP_PKEY structure. */
47 EVP_PKEY * pkey = EVP_PKEY_new();
48 if(!pkey)
49 {
50 std::cerr << "Unable to create EVP_PKEY structure." << std::endl;
51 return NULL;
52 }
53
54 /* Generate the RSA key and assign it to pkey. */
55 RSA * rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL);
56 if(!EVP_PKEY_assign_RSA(pkey, rsa))
57 {
58 std::cerr << "Unable to generate 2048-bit RSA key." << std::endl;
59 EVP_PKEY_free(pkey);
60 return NULL;
61 }
62
63 /* The key has been generated, return it. */
64 return pkey;
65}
66
67int cs_cert_set_subject_alt_name(X509 *x509_cert, std::string name)
68{
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, (unsigned char*) subject_alt_name, strlen(subject_alt_name));
79 if (!X509_EXTENSION_create_by_NID(&extension_san, NID_subject_alt_name, 0, subject_alt_name_ASN1)) {
80 goto err;
81 }
82 ASN1_OCTET_STRING_free(subject_alt_name_ASN1);
83 ret = X509_add_ext(x509_cert, extension_san, -1);
84 if (!ret) {
85 goto err;
86 }
87 X509_EXTENSION_free(extension_san);
88 return 0;
89
90err:
91 if (subject_alt_name_ASN1) ASN1_OCTET_STRING_free(subject_alt_name_ASN1);
92 if (extension_san) X509_EXTENSION_free(extension_san);
93 return -1;
94}
95
96
97/* Generates a self-signed x509 certificate. */
98X509 * generate_x509(EVP_PKEY * pkey, std::string ip_v4)
99{
100 /* Allocate memory for the X509 structure. */
101 X509 * x509 = X509_new();
102 if(!x509)
103 {
104 std::cerr << "Unable to create X509 structure." << std::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 X509_NAME * name = X509_get_subject_name(x509);
121
122 /* Set the country code and common name. */
123 X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"CA", -1, -1, 0);
124 X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"MyCompany", -1, -1, 0);
125 X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"localhost", -1, -1, 0);
126
127#if 0
128 // Here is one way to add SAN records to certificate.
129 // Unfortunately, does not link on Windows. Dunno why...
130 // Alternative method:
131 // cs_cert_set_subject_alt_name(), above.
132
133 GENERAL_NAMES *gens = sk_GENERAL_NAME_new_null();
134 std::string dns_name = "www.example.com";
135 GENERAL_NAME *gen_dns = GENERAL_NAME_new();
136 ASN1_IA5STRING *ia5 = ASN1_IA5STRING_new();
137 ASN1_STRING_set(ia5, dns_name.data(), dns_name.length());
138 GENERAL_NAME_set0_value(gen_dns, GEN_DNS, ia5);
139 sk_GENERAL_NAME_push(gens, gen_dns);
140
141 in_addr_t ipv4 = inet_addr(ip_v4.c_str());
142 GENERAL_NAME *gen_ip = GENERAL_NAME_new();
143 ASN1_OCTET_STRING *octet = ASN1_OCTET_STRING_new();
144 ASN1_STRING_set(octet, &ipv4, sizeof(ipv4));
145 GENERAL_NAME_set0_value(gen_ip, GEN_IPADD, octet);
146 sk_GENERAL_NAME_push(gens, gen_ip);
147
148 X509_add1_ext_i2d(x509, NID_subject_alt_name, gens, 0, X509V3_ADD_DEFAULT);
149
150 sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
151#endif
152
153 std::string ext_name("IP: ");
154 ext_name += ip_v4;
155 cs_cert_set_subject_alt_name(x509, ext_name);
156
157 /* Now set the issuer name. */
158 X509_set_issuer_name(x509, name);
159
160 /* Actually sign the certificate with our key. */
161 if(!X509_sign(x509, pkey, EVP_sha1()))
162 {
163 std::cerr << "Error signing certificate." << std::endl;
164 X509_free(x509);
165 return NULL;
166 }
167
168 return x509;
169}
170
171bool write_to_disk(EVP_PKEY * pkey, X509 * x509, std::string cert_directory)
172{
173 /* Open the PEM file for writing the key to disk. */
174 std::string key_file = cert_directory;
175 key_file += "key.pem";
176 FILE * pkey_file = fopen(key_file.c_str(), "wb");
177 if(!pkey_file)
178 {
179 std::cerr << "Unable to open \"key.pem\" for writing." << std::endl;
180 return false;
181 }
182
183 /* Write the key to disk. */
184 bool ret = PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL);
185 fclose(pkey_file);
186
187 if(!ret)
188 {
189 std::cerr << "Unable to write private key to disk." << std::endl;
190 return false;
191 }
192
193 /* Open the PEM file for writing the certificate to disk. */
194 std::string cert_file = cert_directory;
195 cert_file += "cert.pem";
196
197 FILE * x509_file = fopen(cert_file.c_str(), "wb");
198 if(!x509_file)
199 {
200 std::cerr << "Unable to open \"cert.pem\" for writing." << std::endl;
201 return false;
202 }
203
204 /* Write the certificate to disk. */
205 ret = PEM_write_X509(x509_file, x509);
206 fclose(x509_file);
207
208 if(!ret)
209 {
210 std::cerr << "Unable to write certificate to disk." << std::endl;
211 return false;
212 }
213
214 return true;
215}
216
217int make_certificate(std::string ipv4, std::string destination_dir)
218{
219 /* Generate the key. */
220 std::cout << "Generating RSA key..." << std::endl;
221
222 EVP_PKEY * pkey = generate_key();
223 if(!pkey)
224 return 1;
225
226 /* Generate the certificate. */
227 std::cout << "Generating x509 certificate..." << std::endl;
228
229 X509 * x509 = generate_x509(pkey, ipv4);
230 if(!x509)
231 {
232 EVP_PKEY_free(pkey);
233 return 1;
234 }
235
236 /* Write the private key and certificate out to disk. */
237 std::cout << "Writing key and certificate to disk..." << std::endl;
238
239 bool ret = write_to_disk(pkey, x509, destination_dir);
240 EVP_PKEY_free(pkey);
241 X509_free(x509);
242
243 if(ret)
244 {
245 std::cout << "Success!" << std::endl;
246 return 0;
247 }
248 else
249 return 1;
250}