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