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