]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/KeyData.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / security / KeyData.cc
1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 #include "squid.h"
10 #include "anyp/PortCfg.h"
11 #include "fatal.h"
12 #include "security/KeyData.h"
13 #include "SquidConfig.h"
14 #include "ssl/bio.h"
15
16 /**
17 * Read certificate from file.
18 * See also: Ssl::ReadX509Certificate function, gadgets.cc file
19 */
20 bool
21 Security::KeyData::loadX509CertFromFile()
22 {
23 debugs(83, DBG_IMPORTANT, "Using certificate in " << certFile);
24 cert.reset(); // paranoid: ensure cert is unset
25
26 #if USE_OPENSSL
27 const char *certFilename = certFile.c_str();
28 Ssl::BIO_Pointer bio(BIO_new(BIO_s_file()));
29 if (!bio || !BIO_read_filename(bio.get(), certFilename)) {
30 const auto x = ERR_get_error();
31 debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "': " << ErrorString(x));
32 return false;
33 }
34
35 if (X509 *certificate = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)) {
36 cert.resetWithoutLocking(certificate);
37 }
38
39 #elif USE_GNUTLS
40 const char *certFilename = certFile.c_str();
41 gnutls_datum_t data;
42 Security::LibErrorCode x = gnutls_load_file(certFilename, &data);
43 if (x != GNUTLS_E_SUCCESS) {
44 debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "': " << ErrorString(x));
45 return false;
46 }
47
48 gnutls_pcert_st pcrt;
49 x = gnutls_pcert_import_x509_raw(&pcrt, &data, GNUTLS_X509_FMT_PEM, 0);
50 if (x != GNUTLS_E_SUCCESS) {
51 debugs(83, DBG_IMPORTANT, "ERROR: unable to import certificate from '" << certFile << "': " << ErrorString(x));
52 return false;
53 }
54 gnutls_free(data.data);
55
56 gnutls_x509_crt_t certificate;
57 x = gnutls_pcert_export_x509(&pcrt, &certificate);
58 if (x != GNUTLS_E_SUCCESS) {
59 debugs(83, DBG_IMPORTANT, "ERROR: unable to X.509 convert certificate from '" << certFile << "': " << ErrorString(x));
60 return false;
61 }
62
63 if (certificate) {
64 cert = Security::CertPointer(certificate, [](gnutls_x509_crt_t p) {
65 debugs(83, 5, "gnutls_x509_crt_deinit cert=" << (void*)p);
66 gnutls_x509_crt_deinit(p);
67 });
68 }
69
70 #else
71 // do nothing.
72 #endif
73
74 if (!cert) {
75 debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate from '" << certFile << "'");
76 }
77
78 return bool(cert);
79 }
80
81 /**
82 * Read certificate from file.
83 * See also: Ssl::ReadX509Certificate function, gadgets.cc file
84 */
85 void
86 Security::KeyData::loadX509ChainFromFile()
87 {
88 #if USE_OPENSSL
89 const char *certFilename = certFile.c_str();
90 Ssl::BIO_Pointer bio(BIO_new(BIO_s_file()));
91 if (!bio || !BIO_read_filename(bio.get(), certFilename)) {
92 const auto x = ERR_get_error();
93 debugs(83, DBG_IMPORTANT, "ERROR: unable to load chain file '" << certFile << "': " << ErrorString(x));
94 return;
95 }
96
97 #if TLS_CHAIN_NO_SELFSIGNED // ignore self-signed certs in the chain
98 if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK) {
99 char *nameStr = X509_NAME_oneline(X509_get_subject_name(cert.get()), nullptr, 0);
100 debugs(83, DBG_PARSE_NOTE(2), "Certificate is self-signed, will not be chained: " << nameStr);
101 OPENSSL_free(nameStr);
102 } else
103 #endif
104 {
105 debugs(83, DBG_PARSE_NOTE(3), "Using certificate chain in " << certFile);
106 // and add to the chain any other certificate exist in the file
107 CertPointer latestCert = cert;
108
109 while (auto ca = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)) {
110 // get Issuer name of the cert for debug display
111 char *nameStr = X509_NAME_oneline(X509_get_subject_name(ca), nullptr, 0);
112
113 #if TLS_CHAIN_NO_SELFSIGNED // ignore self-signed certs in the chain
114 // self-signed certificates are not valid in a sent chain
115 if (X509_check_issued(ca, ca) == X509_V_OK) {
116 debugs(83, DBG_PARSE_NOTE(2), "CA " << nameStr << " is self-signed, will not be chained: " << nameStr);
117 OPENSSL_free(nameStr);
118 continue;
119 }
120 #endif
121 // checks that the chained certs are actually part of a chain for validating cert
122 const auto checkCode = X509_check_issued(ca, latestCert.get());
123 if (checkCode == X509_V_OK) {
124 debugs(83, DBG_PARSE_NOTE(3), "Adding issuer CA: " << nameStr);
125 // OpenSSL API requires that we order certificates such that the
126 // chain can be appended directly into the on-wire traffic.
127 latestCert = CertPointer(ca);
128 chain.emplace_front(latestCert);
129 } else {
130 debugs(83, DBG_PARSE_NOTE(2), certFile << ": Ignoring non-issuer CA " << nameStr << ": " << X509_verify_cert_error_string(checkCode) << " (" << checkCode << ")");
131 }
132 OPENSSL_free(nameStr);
133 }
134 }
135
136 #elif USE_GNUTLS
137 // XXX: implement chain loading
138 debugs(83, 2, "Loading certificate chain from PEM files not implemented in this Squid.");
139
140 #else
141 // nothing to do.
142 #endif
143 }
144
145 /**
146 * Read X.509 private key from file.
147 */
148 bool
149 Security::KeyData::loadX509PrivateKeyFromFile()
150 {
151 debugs(83, DBG_IMPORTANT, "Using key in " << privateKeyFile);
152
153 #if USE_OPENSSL
154 const char *keyFilename = privateKeyFile.c_str();
155 // XXX: Ssl::AskPasswordCb needs SSL_CTX_set_default_passwd_cb_userdata()
156 // so this may not fully work iff Config.Program.ssl_password is set.
157 pem_password_cb *cb = ::Config.Program.ssl_password ? &Ssl::AskPasswordCb : nullptr;
158 Ssl::ReadPrivateKeyFromFile(keyFilename, pkey, cb);
159
160 if (pkey && !X509_check_private_key(cert.get(), pkey.get())) {
161 debugs(83, DBG_IMPORTANT, "WARNING: '" << privateKeyFile << "' X509_check_private_key() failed");
162 pkey.reset();
163 }
164
165 #elif USE_GNUTLS
166 const char *keyFilename = privateKeyFile.c_str();
167 gnutls_datum_t data;
168 if (gnutls_load_file(keyFilename, &data) == GNUTLS_E_SUCCESS) {
169 gnutls_privkey_t key;
170 (void)gnutls_privkey_init(&key);
171 Security::ErrorCode x = gnutls_privkey_import_x509_raw(key, &data, GNUTLS_X509_FMT_PEM, nullptr, 0);
172 if (x == GNUTLS_E_SUCCESS) {
173 gnutls_x509_privkey_t xkey;
174 gnutls_privkey_export_x509(key, &xkey);
175 gnutls_privkey_deinit(key);
176 pkey = Security::PrivateKeyPointer(xkey, [](gnutls_x509_privkey_t p) {
177 debugs(83, 5, "gnutls_x509_privkey_deinit pkey=" << (void*)p);
178 gnutls_x509_privkey_deinit(p);
179 });
180 }
181 }
182 gnutls_free(data.data);
183
184 #else
185 // nothing to do.
186 #endif
187
188 return bool(pkey);
189 }
190
191 void
192 Security::KeyData::loadFromFiles(const AnyP::PortCfg &port, const char *portType)
193 {
194 char buf[128];
195 if (!loadX509CertFromFile()) {
196 debugs(83, DBG_IMPORTANT, "WARNING: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' missing certificate in '" << certFile << "'");
197 return;
198 }
199
200 // certificate chain in the PEM file is optional
201 loadX509ChainFromFile();
202
203 // pkey is mandatory, not having it makes cert and chain pointless.
204 if (!loadX509PrivateKeyFromFile()) {
205 debugs(83, DBG_IMPORTANT, "WARNING: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' missing private key in '" << privateKeyFile << "'");
206 cert.reset();
207 chain.clear();
208 }
209 }
210