OpenCPN Partial API docs
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. */
44 EVP_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 
67 int 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 
90 err:
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. */
98 X509 * 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 
171 bool 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 
217 int make_certificate(std::string ipv4, std::string destination_dir)
218 {
219  /* Generate the key. */
220  if (getenv("OCPN_DEBUG_CERT"))
221  std::cout << "Generating RSA key..." << std::endl;
222 
223  EVP_PKEY * pkey = generate_key();
224  if(!pkey)
225  return 1;
226 
227  /* Generate the certificate. */
228  if (getenv("OCPN_DEBUG_CERT"))
229  std::cout << "Generating x509 certificate..." << std::endl;
230 
231  X509 * x509 = generate_x509(pkey, ipv4);
232  if(!x509)
233  {
234  EVP_PKEY_free(pkey);
235  return 1;
236  }
237 
238  /* Write the private key and certificate out to disk. */
239  if (getenv("OCPN_DEBUG_CERT"))
240  std::cout << "Writing key and certificate to disk..." << std::endl;
241 
242  bool ret = write_to_disk(pkey, x509, destination_dir);
243  EVP_PKEY_free(pkey);
244  X509_free(x509);
245 
246  if(ret)
247  {
248  if (getenv("OCPN_DEBUG_CERT")) std::cout << "Success!" << std::endl;
249  return 0;
250  }
251  else
252  return 1;
253 }