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