]>
Commit | Line | Data |
---|---|---|
bbc27441 | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
bbc27441 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 | ||
a1f04d64 | 9 | #include "squid.h" |
77dce8a5 | 10 | #include "acl/FilledChecklist.h" |
4747ea4c | 11 | #include "globals.h" |
a9b6afe1 | 12 | #include "helper.h" |
ac1b16b5 | 13 | #include "sbuf/Stream.h" |
92e3827b | 14 | #include "security/CertError.h" |
a1f04d64 AR |
15 | #include "ssl/cert_validate_message.h" |
16 | #include "ssl/ErrorDetail.h" | |
602d9612 | 17 | #include "ssl/support.h" |
ed6e9fb9 | 18 | #include "util.h" |
a1f04d64 | 19 | |
8fe1a85a CT |
20 | /// Retrieves the certificates chain used to verify the peer. |
21 | /// This is the full chain built by OpenSSL while verifying the server | |
22 | /// certificate or, if this is not available, the chain sent by server. | |
23 | /// \return the certificates chain or nil | |
24 | static STACK_OF(X509) * | |
25 | PeerValidationCertificatesChain(const Security::SessionPointer &ssl) | |
26 | { | |
27 | assert(ssl); | |
28 | // The full chain built by openSSL while verifying the server cert, | |
29 | // retrieved from verify callback: | |
30 | if (const auto certs = static_cast<STACK_OF(X509) *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_ssl_cert_chain))) | |
31 | return certs; | |
32 | ||
33 | /// Last resort: certificates chain sent by server | |
34 | return SSL_get_peer_cert_chain(ssl.get()); // may be nil | |
35 | } | |
36 | ||
3a7d782f | 37 | void |
b56756cb | 38 | Ssl::CertValidationMsg::composeRequest(CertValidationRequest const &vcert) |
a1f04d64 AR |
39 | { |
40 | body.clear(); | |
b56756cb | 41 | body += Ssl::CertValidationMsg::param_host + "=" + vcert.domainName; |
4747ea4c | 42 | |
0b168d25 | 43 | if (const char *sslVersion = SSL_get_version(vcert.ssl.get())) |
6e325882 CT |
44 | body += "\n" + Ssl::CertValidationMsg::param_proto_version + "=" + sslVersion; |
45 | ||
0b168d25 | 46 | if (const char *cipherName = SSL_CIPHER_get_name(SSL_get_current_cipher(vcert.ssl.get()))) |
6e325882 CT |
47 | body += "\n" + Ssl::CertValidationMsg::param_cipher + "=" + cipherName; |
48 | ||
8fe1a85a | 49 | STACK_OF(X509) *peerCerts = PeerValidationCertificatesChain(vcert.ssl); |
14798e73 | 50 | if (peerCerts) { |
a1f04d64 | 51 | Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem())); |
14798e73 CT |
52 | for (int i = 0; i < sk_X509_num(peerCerts); ++i) { |
53 | X509 *cert = sk_X509_value(peerCerts, i); | |
a1f04d64 | 54 | PEM_write_bio_X509(bio.get(), cert); |
62a7607e | 55 | body = body + "\n" + param_cert + xitoa(i) + "="; |
a1f04d64 AR |
56 | char *ptr; |
57 | long len = BIO_get_mem_data(bio.get(), &ptr); | |
62a7607e | 58 | body.append(ptr, (ptr[len-1] == '\n' ? len - 1 : len)); |
a1f04d64 AR |
59 | if (!BIO_reset(bio.get())) { |
60 | // print an error? | |
61 | } | |
62 | } | |
63 | } | |
62a7607e CT |
64 | |
65 | if (vcert.errors) { | |
66 | int i = 0; | |
92e3827b | 67 | for (const Security::CertErrors *err = vcert.errors; err; err = err->next, ++i) { |
62a7607e CT |
68 | body +="\n"; |
69 | body = body + param_error_name + xitoa(i) + "=" + GetErrorName(err->element.code) + "\n"; | |
70 | int errorCertPos = -1; | |
170cb017 | 71 | if (err->element.cert.get()) |
62a7607e CT |
72 | errorCertPos = sk_X509_find(peerCerts, err->element.cert.get()); |
73 | if (errorCertPos < 0) { | |
74 | // assert this error ? | |
75 | debugs(83, 4, "WARNING: wrong cert in cert validator request"); | |
76 | } | |
77 | body += param_error_cert + xitoa(i) + "="; | |
78 | body += param_cert + xitoa((errorCertPos >= 0 ? errorCertPos : 0)); | |
79 | } | |
80 | } | |
a1f04d64 AR |
81 | } |
82 | ||
3a7d782f CT |
83 | static int |
84 | get_error_id(const char *label, size_t len) | |
a1f04d64 AR |
85 | { |
86 | const char *e = label + len -1; | |
87 | while (e != label && xisdigit(*e)) --e; | |
88 | if (e != label) ++e; | |
aee3523a | 89 | return strtol(e, nullptr, 10); |
a1f04d64 AR |
90 | } |
91 | ||
3a7d782f | 92 | bool |
8b082ed9 | 93 | Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp) |
ac1b16b5 AR |
94 | { |
95 | try { | |
96 | tryParsingResponse(resp); | |
97 | return true; | |
98 | } | |
99 | catch (...) { | |
100 | debugs(83, DBG_IMPORTANT, "ERROR: Cannot parse sslcrtvalidator_program response:" << | |
101 | Debug::Extra << "problem: " << CurrentException); | |
102 | return false; | |
103 | } | |
104 | } | |
105 | ||
106 | /// implements primary parseResponse() functionality until that method callers | |
107 | /// are ready to handle exceptions | |
108 | void | |
109 | Ssl::CertValidationMsg::tryParsingResponse(CertValidationResponse &resp) | |
a1f04d64 | 110 | { |
3a7d782f | 111 | std::vector<CertItem> certs; |
a1f04d64 | 112 | |
8fe1a85a CT |
113 | const STACK_OF(X509) *peerCerts = PeerValidationCertificatesChain(resp.ssl); |
114 | ||
a1f04d64 | 115 | const char *param = body.c_str(); |
22636a68 CT |
116 | while (*param) { |
117 | while (xisspace(*param)) param++; | |
a1f04d64 AR |
118 | if (! *param) |
119 | break; | |
120 | ||
121 | size_t param_len = strcspn(param, "=\r\n"); | |
122 | if (param[param_len] != '=') { | |
ac1b16b5 | 123 | throw TextException(ToSBuf("Missing parameter value: ", param), Here()); |
a1f04d64 AR |
124 | } |
125 | const char *value=param+param_len+1; | |
126 | ||
22636a68 CT |
127 | if (param_len > param_cert.length() && |
128 | strncmp(param, param_cert.c_str(), param_cert.length()) == 0) { | |
3a7d782f | 129 | CertItem ci; |
22636a68 | 130 | ci.name.assign(param, param_len); |
ac1b16b5 | 131 | const auto x509 = ReadCertificate(ReadOnlyBioTiedTo(value)); |
a1f04d64 AR |
132 | ci.setCert(x509.get()); |
133 | certs.push_back(ci); | |
134 | ||
135 | const char *b = strstr(value, "-----END CERTIFICATE-----"); | |
aee3523a | 136 | if (b == nullptr) { |
ac1b16b5 | 137 | throw TextException(ToSBuf("Missing END CERTIFICATE boundary: ", value), Here()); |
a1f04d64 AR |
138 | } |
139 | b += strlen("-----END CERTIFICATE-----"); | |
140 | param = b + 1; | |
141 | continue; | |
142 | } | |
143 | ||
144 | size_t value_len = strcspn(value, "\r\n"); | |
145 | std::string v(value, value_len); | |
146 | ||
4a77bb4e | 147 | debugs(83, 5, "Returned value: " << std::string(param, param_len).c_str() << ": " << |
a1f04d64 AR |
148 | v.c_str()); |
149 | ||
150 | int errorId = get_error_id(param, param_len); | |
b56756cb | 151 | Ssl::CertValidationResponse::RecvdError ¤tItem = resp.getError(errorId); |
a1f04d64 | 152 | |
22636a68 CT |
153 | if (param_len > param_error_name.length() && |
154 | strncmp(param, param_error_name.c_str(), param_error_name.length()) == 0) { | |
a1f04d64 | 155 | currentItem.error_no = Ssl::GetErrorCode(v.c_str()); |
3a7d782f | 156 | if (currentItem.error_no == SSL_ERROR_NONE) { |
ac1b16b5 | 157 | throw TextException(ToSBuf("Unknown TLS error name: ", v), Here()); |
3a7d782f | 158 | } |
22636a68 | 159 | } else if (param_len > param_error_reason.length() && |
a1f04d64 AR |
160 | strncmp(param, param_error_reason.c_str(), param_error_reason.length()) == 0) { |
161 | currentItem.error_reason = v; | |
162 | } else if (param_len > param_error_cert.length() && | |
163 | strncmp(param, param_error_cert.c_str(), param_error_cert.length()) == 0) { | |
164 | ||
3a7d782f | 165 | if (X509 *cert = getCertByName(certs, v)) { |
4a77bb4e | 166 | debugs(83, 6, "The certificate with id \"" << v << "\" found."); |
3a7d782f CT |
167 | currentItem.setCert(cert); |
168 | } else { | |
169 | //In this case we assume that the certID is one of the certificates sent | |
170 | // to cert validator. The certificates sent to cert validator have names in | |
171 | // form "cert_xx" where the "xx" is an integer represents the position of | |
172 | // the certificate inside peer certificates list. | |
b56756cb | 173 | const int certId = get_error_id(v.c_str(), v.length()); |
4a77bb4e | 174 | debugs(83, 6, "Cert index in peer certificates list:" << certId); |
77dce8a5 CT |
175 | //if certId is not correct sk_X509_value returns NULL |
176 | currentItem.setCert(sk_X509_value(peerCerts, certId)); | |
a1f04d64 | 177 | } |
b4e6a8d4 CT |
178 | } else if (param_len > param_error_depth.length() && |
179 | strncmp(param, param_error_depth.c_str(), param_error_depth.length()) == 0 && | |
180 | std::all_of(v.begin(), v.end(), isdigit)) { | |
96e44f2e | 181 | currentItem.error_depth = atoi(v.c_str()); |
3a7d782f | 182 | } else { |
ac1b16b5 | 183 | throw TextException(ToSBuf("Unknown parameter name: ", std::string(param, param_len)), Here()); |
3a7d782f | 184 | } |
a1f04d64 | 185 | |
77da1d91 | 186 | param = value + value_len; |
a1f04d64 AR |
187 | } |
188 | ||
3a7d782f | 189 | /*Run through parsed errors to check for errors*/ |
bc624572 CT |
190 | typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI; |
191 | for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) { | |
46901eed | 192 | if (i->error_no == SSL_ERROR_NONE) { |
ac1b16b5 | 193 | throw TextException(ToSBuf("Incomplete response; missing error name from error_id: ", i->id), Here()); |
bc624572 CT |
194 | } |
195 | } | |
a1f04d64 AR |
196 | } |
197 | ||
3a7d782f | 198 | X509 * |
b56756cb | 199 | Ssl::CertValidationMsg::getCertByName(std::vector<CertItem> const &certs, std::string const & name) |
3a7d782f | 200 | { |
6136ab56 CT |
201 | typedef std::vector<CertItem>::const_iterator SVCI; |
202 | for (SVCI ci = certs.begin(); ci != certs.end(); ++ci) { | |
3a7d782f | 203 | if (ci->name.compare(name) == 0) |
4a77bb4e | 204 | return ci->cert.get(); |
3a7d782f | 205 | } |
aee3523a | 206 | return nullptr; |
3a7d782f CT |
207 | } |
208 | ||
72247610 AJ |
209 | uint64_t |
210 | Ssl::CertValidationResponse::MemoryUsedByResponse(const CertValidationResponse::Pointer &) | |
211 | { | |
212 | // XXX: This math does not account for most of the response size! | |
213 | return sizeof(CertValidationResponse); | |
214 | } | |
215 | ||
b56756cb CT |
216 | Ssl::CertValidationResponse::RecvdError & |
217 | Ssl::CertValidationResponse::getError(int errorId) | |
3a7d782f | 218 | { |
6136ab56 | 219 | typedef Ssl::CertValidationResponse::RecvdErrors::iterator SVCREI; |
22636a68 | 220 | for (SVCREI i = errors.begin(); i != errors.end(); ++i) { |
3a7d782f CT |
221 | if (i->id == errorId) |
222 | return *i; | |
223 | } | |
b56756cb | 224 | Ssl::CertValidationResponse::RecvdError errItem; |
3a7d782f CT |
225 | errItem.id = errorId; |
226 | errors.push_back(errItem); | |
227 | return errors.back(); | |
228 | } | |
229 | ||
a1f04d64 | 230 | void |
22636a68 CT |
231 | Ssl::CertValidationResponse::RecvdError::setCert(X509 *aCert) |
232 | { | |
4a77bb4e | 233 | cert.resetAndLock(aCert); |
a1f04d64 AR |
234 | } |
235 | ||
a1f04d64 | 236 | void |
b56756cb | 237 | Ssl::CertValidationMsg::CertItem::setCert(X509 *aCert) |
a1f04d64 | 238 | { |
4a77bb4e | 239 | cert.resetAndLock(aCert); |
a1f04d64 AR |
240 | } |
241 | ||
b56756cb CT |
242 | const std::string Ssl::CertValidationMsg::code_cert_validate("cert_validate"); |
243 | const std::string Ssl::CertValidationMsg::param_domain("domain"); | |
b56756cb | 244 | const std::string Ssl::CertValidationMsg::param_cert("cert_"); |
22636a68 | 245 | const std::string Ssl::CertValidationMsg::param_error_name("error_name_"); |
b56756cb CT |
246 | const std::string Ssl::CertValidationMsg::param_error_reason("error_reason_"); |
247 | const std::string Ssl::CertValidationMsg::param_error_cert("error_cert_"); | |
b4e6a8d4 | 248 | const std::string Ssl::CertValidationMsg::param_error_depth("error_depth_"); |
6e325882 CT |
249 | const std::string Ssl::CertValidationMsg::param_proto_version("proto_version"); |
250 | const std::string Ssl::CertValidationMsg::param_cipher("cipher"); | |
f53969cc | 251 |