]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/KeyData.cc
Sort CA certificates in tls-cert=bundle (#1177)
[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 the signing certificate and its chain, if any, from certFile
19 /// \return true if the signing certificate was obtained
20 bool
21 Security::KeyData::loadCertificates()
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 try {
36 cert = Ssl::ReadCertificate(bio);
37 debugs(83, DBG_PARSE_NOTE(2), "Loaded signing certificate: " << *cert);
38 }
39 catch (...) {
40 // TODO: Convert the rest of this method to throw on errors instead.
41 debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "':" <<
42 Debug::Extra << "problem: " << CurrentException);
43 return false;
44 }
45
46 try {
47 // Squid sends `cert` (loaded above) followed by certificates in `chain`
48 // (formed below by loading and sorting the remaining certificates).
49
50 // load all the remaining configured certificates
51 CertList candidates;
52 while (const auto c = Ssl::ReadOptionalCertificate(bio))
53 candidates.emplace_back(c);
54
55 // Push certificates into `chain` in on-the-wire order, as defined by
56 // RFC 8446 Section 4.4.2: "Each following certificate SHOULD directly
57 // certify the one immediately preceding it."
58 while (!candidates.empty()) {
59 const auto precedingCert = chain.empty() ? cert : chain.back();
60
61 // We cannot chain any certificate after a self-signed certificate.
62 // This check also protects the IssuedBy() search below from adding
63 // duplicated (i.e. listed multiple times) self-signed certificates.
64 if (SelfSigned(*precedingCert))
65 break;
66
67 const auto issuerPos = std::find_if(candidates.begin(), candidates.end(), [&](const CertPointer &i) {
68 return IssuedBy(*precedingCert, *i);
69 });
70 if (issuerPos == candidates.end())
71 break;
72
73 const auto &issuer = *issuerPos;
74 debugs(83, DBG_PARSE_NOTE(3), "Adding CA certificate: " << *issuer);
75 chain.emplace_back(issuer);
76 candidates.erase(issuerPos);
77 }
78
79 for (const auto &c: candidates)
80 debugs(83, DBG_IMPORTANT, "WARNING: Ignoring certificate that does not extend the chain: " << *c);
81 }
82 catch (...) {
83 // TODO: Reject configs with malformed intermediate certs instead.
84 debugs(83, DBG_IMPORTANT, "ERROR: Failure while loading intermediate certificate(s) from '" << certFile << "':" <<
85 Debug::Extra << "problem: " << CurrentException);
86 }
87
88 #elif USE_GNUTLS
89 const char *certFilename = certFile.c_str();
90 gnutls_datum_t data;
91 Security::LibErrorCode x = gnutls_load_file(certFilename, &data);
92 if (x != GNUTLS_E_SUCCESS) {
93 debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "': " << ErrorString(x));
94 return false;
95 }
96
97 gnutls_pcert_st pcrt;
98 x = gnutls_pcert_import_x509_raw(&pcrt, &data, GNUTLS_X509_FMT_PEM, 0);
99 if (x != GNUTLS_E_SUCCESS) {
100 debugs(83, DBG_IMPORTANT, "ERROR: unable to import certificate from '" << certFile << "': " << ErrorString(x));
101 return false;
102 }
103 gnutls_free(data.data);
104
105 gnutls_x509_crt_t certificate;
106 x = gnutls_pcert_export_x509(&pcrt, &certificate);
107 if (x != GNUTLS_E_SUCCESS) {
108 debugs(83, DBG_IMPORTANT, "ERROR: unable to X.509 convert certificate from '" << certFile << "': " << ErrorString(x));
109 return false;
110 }
111
112 if (certificate) {
113 cert = Security::CertPointer(certificate, [](gnutls_x509_crt_t p) {
114 debugs(83, 5, "gnutls_x509_crt_deinit cert=" << (void*)p);
115 gnutls_x509_crt_deinit(p);
116 });
117 }
118
119 // XXX: implement chain loading
120 debugs(83, 2, "Loading certificate chain from PEM files not implemented in this Squid.");
121
122 #else
123 // do nothing.
124 #endif
125
126 if (!cert) {
127 debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate from '" << certFile << "'");
128 }
129
130 return bool(cert);
131 }
132
133 /**
134 * Read X.509 private key from file.
135 */
136 bool
137 Security::KeyData::loadX509PrivateKeyFromFile()
138 {
139 debugs(83, DBG_IMPORTANT, "Using key in " << privateKeyFile);
140
141 #if USE_OPENSSL
142 const char *keyFilename = privateKeyFile.c_str();
143 // XXX: Ssl::AskPasswordCb needs SSL_CTX_set_default_passwd_cb_userdata()
144 // so this may not fully work iff Config.Program.ssl_password is set.
145 pem_password_cb *cb = ::Config.Program.ssl_password ? &Ssl::AskPasswordCb : nullptr;
146 Ssl::ReadPrivateKeyFromFile(keyFilename, pkey, cb);
147
148 if (pkey && !X509_check_private_key(cert.get(), pkey.get())) {
149 debugs(83, DBG_IMPORTANT, "WARNING: '" << privateKeyFile << "' X509_check_private_key() failed");
150 pkey.reset();
151 }
152
153 #elif USE_GNUTLS
154 const char *keyFilename = privateKeyFile.c_str();
155 gnutls_datum_t data;
156 if (gnutls_load_file(keyFilename, &data) == GNUTLS_E_SUCCESS) {
157 gnutls_privkey_t key;
158 (void)gnutls_privkey_init(&key);
159 Security::ErrorCode x = gnutls_privkey_import_x509_raw(key, &data, GNUTLS_X509_FMT_PEM, nullptr, 0);
160 if (x == GNUTLS_E_SUCCESS) {
161 gnutls_x509_privkey_t xkey;
162 gnutls_privkey_export_x509(key, &xkey);
163 gnutls_privkey_deinit(key);
164 pkey = Security::PrivateKeyPointer(xkey, [](gnutls_x509_privkey_t p) {
165 debugs(83, 5, "gnutls_x509_privkey_deinit pkey=" << (void*)p);
166 gnutls_x509_privkey_deinit(p);
167 });
168 }
169 }
170 gnutls_free(data.data);
171
172 #else
173 // nothing to do.
174 #endif
175
176 return bool(pkey);
177 }
178
179 void
180 Security::KeyData::loadFromFiles(const AnyP::PortCfg &port, const char *portType)
181 {
182 char buf[128];
183 if (!loadCertificates()) {
184 debugs(83, DBG_IMPORTANT, "WARNING: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' missing certificate in '" << certFile << "'");
185 return;
186 }
187
188 // pkey is mandatory, not having it makes cert and chain pointless.
189 if (!loadX509PrivateKeyFromFile()) {
190 debugs(83, DBG_IMPORTANT, "WARNING: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' missing private key in '" << privateKeyFile << "'");
191 cert.reset();
192 chain.clear();
193 }
194 }
195