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