2 * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
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.
10 #include "anyp/PortCfg.h"
12 #include "security/Certificate.h"
13 #include "security/KeyData.h"
14 #include "SquidConfig.h"
16 #include "ssl/gadgets.h"
18 /// load the signing certificate and its chain, if any, from certFile
19 /// \return true if the signing certificate was obtained
21 Security::KeyData::loadCertificates()
23 debugs(83, DBG_IMPORTANT
, "Using certificate in " << certFile
);
24 cert
.reset(); // paranoid: ensure cert is unset
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
));
36 cert
= Ssl::ReadCertificate(bio
);
37 debugs(83, DBG_PARSE_NOTE(2), "Loaded signing certificate: " << *cert
);
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
);
47 // Squid sends `cert` (loaded above) followed by certificates in `chain`
48 // (formed below by loading and sorting the remaining certificates).
50 // load all the remaining configured certificates
52 while (const auto c
= Ssl::ReadOptionalCertificate(bio
))
53 candidates
.emplace_back(c
);
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();
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
))
67 const auto issuerPos
= std::find_if(candidates
.begin(), candidates
.end(), [&](const CertPointer
&i
) {
68 return IssuedBy(*precedingCert
, *i
);
70 if (issuerPos
== candidates
.end())
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
);
79 for (const auto &c
: candidates
)
80 debugs(83, DBG_IMPORTANT
, "WARNING: Ignoring certificate that does not extend the chain: " << *c
);
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
);
89 const char *certFilename
= certFile
.c_str();
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
));
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
));
103 gnutls_free(data
.data
);
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
));
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
);
119 // XXX: implement chain loading
120 debugs(83, 2, "Loading certificate chain from PEM files not implemented in this Squid.");
127 debugs(83, DBG_IMPORTANT
, "ERROR: unable to load certificate from '" << certFile
<< "'");
134 * Read X.509 private key from file.
137 Security::KeyData::loadX509PrivateKeyFromFile()
139 debugs(83, DBG_IMPORTANT
, "Using key in " << privateKeyFile
);
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
);
148 if (pkey
&& !X509_check_private_key(cert
.get(), pkey
.get())) {
149 debugs(83, DBG_IMPORTANT
, "WARNING: '" << privateKeyFile
<< "' X509_check_private_key() failed");
154 const char *keyFilename
= privateKeyFile
.c_str();
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
);
170 gnutls_free(data
.data
);
180 Security::KeyData::loadFromFiles(const AnyP::PortCfg
&port
, const char *portType
)
183 if (!loadCertificates()) {
184 debugs(83, DBG_IMPORTANT
, "WARNING: '" << portType
<< "_port " << port
.s
.toUrl(buf
, sizeof(buf
)) << "' missing certificate in '" << certFile
<< "'");
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
<< "'");