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 "acl/FilledChecklist.h"
13 #include "sbuf/Stream.h"
14 #include "security/CertError.h"
15 #include "ssl/cert_validate_message.h"
16 #include "ssl/ErrorDetail.h"
17 #include "ssl/support.h"
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
)
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
)))
33 /// Last resort: certificates chain sent by server
34 return SSL_get_peer_cert_chain(ssl
.get()); // may be nil
38 Ssl::CertValidationMsg::composeRequest(CertValidationRequest
const &vcert
)
41 body
+= Ssl::CertValidationMsg::param_host
+ "=" + vcert
.domainName
;
43 if (const char *sslVersion
= SSL_get_version(vcert
.ssl
.get()))
44 body
+= "\n" + Ssl::CertValidationMsg::param_proto_version
+ "=" + sslVersion
;
46 if (const char *cipherName
= SSL_CIPHER_get_name(SSL_get_current_cipher(vcert
.ssl
.get())))
47 body
+= "\n" + Ssl::CertValidationMsg::param_cipher
+ "=" + cipherName
;
49 STACK_OF(X509
) *peerCerts
= PeerValidationCertificatesChain(vcert
.ssl
);
51 Ssl::BIO_Pointer
bio(BIO_new(BIO_s_mem()));
52 for (int i
= 0; i
< sk_X509_num(peerCerts
); ++i
) {
53 X509
*cert
= sk_X509_value(peerCerts
, i
);
54 PEM_write_bio_X509(bio
.get(), cert
);
55 body
= body
+ "\n" + param_cert
+ xitoa(i
) + "=";
57 long len
= BIO_get_mem_data(bio
.get(), &ptr
);
58 body
.append(ptr
, (ptr
[len
-1] == '\n' ? len
- 1 : len
));
59 if (!BIO_reset(bio
.get())) {
67 for (const Security::CertErrors
*err
= vcert
.errors
; err
; err
= err
->next
, ++i
) {
69 body
= body
+ param_error_name
+ xitoa(i
) + "=" + GetErrorName(err
->element
.code
) + "\n";
70 int errorCertPos
= -1;
71 if (err
->element
.cert
.get())
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");
77 body
+= param_error_cert
+ xitoa(i
) + "=";
78 body
+= param_cert
+ xitoa((errorCertPos
>= 0 ? errorCertPos
: 0));
84 get_error_id(const char *label
, size_t len
)
86 const char *e
= label
+ len
-1;
87 while (e
!= label
&& xisdigit(*e
)) --e
;
89 return strtol(e
, nullptr, 10);
93 Ssl::CertValidationMsg::parseResponse(CertValidationResponse
&resp
)
96 tryParsingResponse(resp
);
100 debugs(83, DBG_IMPORTANT
, "ERROR: Cannot parse sslcrtvalidator_program response:" <<
101 Debug::Extra
<< "problem: " << CurrentException
);
106 /// implements primary parseResponse() functionality until that method callers
107 /// are ready to handle exceptions
109 Ssl::CertValidationMsg::tryParsingResponse(CertValidationResponse
&resp
)
111 std::vector
<CertItem
> certs
;
113 const STACK_OF(X509
) *peerCerts
= PeerValidationCertificatesChain(resp
.ssl
);
115 const char *param
= body
.c_str();
117 while (xisspace(*param
)) param
++;
121 size_t param_len
= strcspn(param
, "=\r\n");
122 if (param
[param_len
] != '=') {
123 throw TextException(ToSBuf("Missing parameter value: ", param
), Here());
125 const char *value
=param
+param_len
+1;
127 if (param_len
> param_cert
.length() &&
128 strncmp(param
, param_cert
.c_str(), param_cert
.length()) == 0) {
130 ci
.name
.assign(param
, param_len
);
131 const auto x509
= ReadCertificate(ReadOnlyBioTiedTo(value
));
132 ci
.setCert(x509
.get());
135 const char *b
= strstr(value
, "-----END CERTIFICATE-----");
137 throw TextException(ToSBuf("Missing END CERTIFICATE boundary: ", value
), Here());
139 b
+= strlen("-----END CERTIFICATE-----");
144 size_t value_len
= strcspn(value
, "\r\n");
145 std::string
v(value
, value_len
);
147 debugs(83, 5, "Returned value: " << std::string(param
, param_len
).c_str() << ": " <<
150 int errorId
= get_error_id(param
, param_len
);
151 Ssl::CertValidationResponse::RecvdError
¤tItem
= resp
.getError(errorId
);
153 if (param_len
> param_error_name
.length() &&
154 strncmp(param
, param_error_name
.c_str(), param_error_name
.length()) == 0) {
155 currentItem
.error_no
= Ssl::GetErrorCode(v
.c_str());
156 if (currentItem
.error_no
== SSL_ERROR_NONE
) {
157 throw TextException(ToSBuf("Unknown TLS error name: ", v
), Here());
159 } else if (param_len
> param_error_reason
.length() &&
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) {
165 if (X509
*cert
= getCertByName(certs
, v
)) {
166 debugs(83, 6, "The certificate with id \"" << v
<< "\" found.");
167 currentItem
.setCert(cert
);
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.
173 const int certId
= get_error_id(v
.c_str(), v
.length());
174 debugs(83, 6, "Cert index in peer certificates list:" << certId
);
175 //if certId is not correct sk_X509_value returns NULL
176 currentItem
.setCert(sk_X509_value(peerCerts
, certId
));
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
)) {
181 currentItem
.error_depth
= atoi(v
.c_str());
183 throw TextException(ToSBuf("Unknown parameter name: ", std::string(param
, param_len
)), Here());
186 param
= value
+ value_len
;
189 /*Run through parsed errors to check for errors*/
190 typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI
;
191 for (SVCRECI i
= resp
.errors
.begin(); i
!= resp
.errors
.end(); ++i
) {
192 if (i
->error_no
== SSL_ERROR_NONE
) {
193 throw TextException(ToSBuf("Incomplete response; missing error name from error_id: ", i
->id
), Here());
199 Ssl::CertValidationMsg::getCertByName(std::vector
<CertItem
> const &certs
, std::string
const & name
)
201 typedef std::vector
<CertItem
>::const_iterator SVCI
;
202 for (SVCI ci
= certs
.begin(); ci
!= certs
.end(); ++ci
) {
203 if (ci
->name
.compare(name
) == 0)
204 return ci
->cert
.get();
210 Ssl::CertValidationResponse::MemoryUsedByResponse(const CertValidationResponse::Pointer
&)
212 // XXX: This math does not account for most of the response size!
213 return sizeof(CertValidationResponse
);
216 Ssl::CertValidationResponse::RecvdError
&
217 Ssl::CertValidationResponse::getError(int errorId
)
219 typedef Ssl::CertValidationResponse::RecvdErrors::iterator SVCREI
;
220 for (SVCREI i
= errors
.begin(); i
!= errors
.end(); ++i
) {
221 if (i
->id
== errorId
)
224 Ssl::CertValidationResponse::RecvdError errItem
;
225 errItem
.id
= errorId
;
226 errors
.push_back(errItem
);
227 return errors
.back();
231 Ssl::CertValidationResponse::RecvdError::setCert(X509
*aCert
)
233 cert
.resetAndLock(aCert
);
237 Ssl::CertValidationMsg::CertItem::setCert(X509
*aCert
)
239 cert
.resetAndLock(aCert
);
242 const std::string
Ssl::CertValidationMsg::code_cert_validate("cert_validate");
243 const std::string
Ssl::CertValidationMsg::param_domain("domain");
244 const std::string
Ssl::CertValidationMsg::param_cert("cert_");
245 const std::string
Ssl::CertValidationMsg::param_error_name("error_name_");
246 const std::string
Ssl::CertValidationMsg::param_error_reason("error_reason_");
247 const std::string
Ssl::CertValidationMsg::param_error_cert("error_cert_");
248 const std::string
Ssl::CertValidationMsg::param_error_depth("error_depth_");
249 const std::string
Ssl::CertValidationMsg::param_proto_version("proto_version");
250 const std::string
Ssl::CertValidationMsg::param_cipher("cipher");