GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_curl.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 5 162 3.1%
Branches: 1 187 0.5%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4 #define __STDC_FORMAT_MACROS
5 #include "authz_curl.h"
6
7 #include <openssl/err.h>
8 #include <openssl/ssl.h>
9 #include <pthread.h>
10
11 #include <cassert>
12
13 #include "authz/authz_session.h"
14 #include "crypto/openssl_version.h"
15 #include "duplex_curl.h"
16 #include "util/concurrency.h"
17 #include "util/logging.h"
18 #include "util/pointer.h"
19
20 using namespace std; // NOLINT
21
22
23 namespace {
24
25 struct sslctx_info {
26 sslctx_info() : chain(NULL), pkey(NULL) { }
27
28 STACK_OF(X509) * chain;
29 EVP_PKEY *pkey;
30 };
31
32 struct bearer_info {
33 /**
34 * List of extra headers to put on the HTTP request. This is required
35 * in order to add the "Authorization: Bearer XXXXX" header.
36 */
37 struct curl_slist *list;
38
39 /**
40 * Actual text of the bearer token
41 */
42 char *token;
43 };
44 } // anonymous namespace
45
46
47 bool AuthzAttachment::ssl_strings_loaded_ = false;
48
49
50 1662 AuthzAttachment::AuthzAttachment(AuthzSessionManager *sm)
51 1662 : authz_session_manager_(sm) {
52 // Required for logging OpenSSL errors
53
1/2
✓ Branch 1 taken 1662 times.
✗ Branch 2 not taken.
1662 SSL_load_error_strings();
54 1662 ssl_strings_loaded_ = true;
55 1662 }
56
57
58 CURLcode AuthzAttachment::CallbackSslCtx(CURL *curl, void *sslctx, void *parm) {
59 sslctx_info *p = reinterpret_cast<sslctx_info *>(parm);
60 SSL_CTX *ctx = reinterpret_cast<SSL_CTX *>(sslctx);
61
62 if (parm == NULL)
63 return CURLE_OK;
64
65 STACK_OF(X509) *chain = p->chain;
66 EVP_PKEY *pkey = p->pkey;
67
68 LogCvmfs(kLogAuthz, kLogDebug, "Customizing OpenSSL context.");
69
70 const int cert_count = sk_X509_num(chain);
71 if (cert_count == 0) {
72 LogOpenSSLErrors("No certificate found in chain.");
73 }
74 X509 *cert = sk_X509_value(chain, 0);
75
76 // NOTE: SSL_CTX_use_certificate and _user_PrivateKey increase the ref count.
77 if (!SSL_CTX_use_certificate(ctx, cert)) {
78 LogOpenSSLErrors("Failed to set the user certificate in the SSL "
79 "connection");
80 return CURLE_SSL_CERTPROBLEM;
81 }
82
83 if (!SSL_CTX_use_PrivateKey(ctx, pkey)) {
84 LogOpenSSLErrors("Failed to set the private key in the SSL connection");
85 return CURLE_SSL_CERTPROBLEM;
86 }
87
88 if (!SSL_CTX_check_private_key(ctx)) {
89 LogOpenSSLErrors("Provided certificate and key do not match");
90 return CURLE_SSL_CERTPROBLEM;
91 } else {
92 LogCvmfs(kLogAuthz, kLogDebug, "Client certificate and key match.");
93 }
94
95 // NOTE: SSL_CTX_add_extra_chain_cert DOES NOT increase the ref count
96 // Instead, it now owns the pointer. THIS IS DIFFERENT FROM ABOVE.
97 for (int idx = 1; idx < cert_count; idx++) {
98 cert = sk_X509_value(chain, idx);
99 if (!SSL_CTX_add_extra_chain_cert(ctx, X509_dup(cert))) {
100 LogOpenSSLErrors("Failed to add client cert to chain");
101 }
102 }
103
104 return CURLE_OK;
105 }
106
107
108 bool AuthzAttachment::ConfigureSciTokenCurl(CURL *curl_handle,
109 const AuthzToken &token,
110 void **info_data) {
111 if (*info_data == NULL) {
112 AuthzToken *saved_token = new AuthzToken();
113 saved_token->type = kTokenBearer;
114 saved_token->data = new bearer_info;
115 bearer_info *bearer = static_cast<bearer_info *>(saved_token->data);
116 bearer->list = NULL;
117 bearer->token = static_cast<char *>(
118 smalloc((sizeof(char) * token.size) + 1));
119 memcpy(bearer->token, token.data, token.size);
120 static_cast<char *>(bearer->token)[token.size] = 0;
121 *info_data = saved_token;
122 }
123
124 AuthzToken *tmp_token = static_cast<AuthzToken *>(*info_data);
125 bearer_info *bearer = static_cast<bearer_info *>(tmp_token->data);
126
127 LogCvmfs(kLogAuthz, kLogDebug, "Setting OAUTH bearer token to: %s",
128 static_cast<char *>(bearer->token));
129
130 // Create the Bearer token
131 // The CURLOPT_XOAUTH2_BEARER option only works "IMAP, POP3 and SMTP"
132 // protocols. Not HTTPS
133 const std::string auth_preamble = "Authorization: Bearer ";
134 const std::string auth_header =
135 auth_preamble + static_cast<char *>(bearer->token);
136 bearer->list = curl_slist_append(bearer->list, auth_header.c_str());
137 const int retval =
138 curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, bearer->list);
139
140 if (retval != CURLE_OK) {
141 LogCvmfs(kLogAuthz, kLogSyslogErr, "Failed to set Oauth2 Bearer Token");
142 return false;
143 }
144
145 return true;
146 }
147
148
149 bool AuthzAttachment::ConfigureCurlHandle(CURL *curl_handle,
150 pid_t pid,
151 void **info_data) {
152 assert(info_data);
153
154 // File catalog has no membership requirement, no tokens to attach
155 if (membership_.empty())
156 return false;
157
158 // We cannot rely on libcurl to pipeline (yet), as cvmfs may
159 // bounce between different auth handles.
160 curl_easy_setopt(curl_handle, CURLOPT_FRESH_CONNECT, 1);
161 curl_easy_setopt(curl_handle, CURLOPT_FORBID_REUSE, 1);
162 curl_easy_setopt(curl_handle, CURLOPT_SSL_SESSIONID_CACHE, 0);
163
164 const UniquePtr<AuthzToken> token(
165 authz_session_manager_->GetTokenCopy(pid, membership_));
166 if (!token.IsValid()) {
167 LogCvmfs(kLogAuthz, kLogDebug, "failed to get authz token for pid %d", pid);
168 return false;
169 }
170
171 switch (token->type) {
172 case kTokenBearer:
173 // If it's a scitoken, then just go to the private
174 // ConfigureSciTokenCurl function
175 return ConfigureSciTokenCurl(curl_handle, *token, info_data);
176
177 case kTokenX509:
178 // The x509 code is below, so just break and go.
179 break;
180
181 default:
182 // Oh no, don't know the the token type, throw error and return
183 LogCvmfs(kLogAuthz, kLogDebug, "unknown token type: %d", token->type);
184 return false;
185 }
186
187 curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA, NULL);
188
189 // The calling layer is reusing data;
190 if (*info_data) {
191 curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA,
192 static_cast<AuthzToken *>(*info_data)->data);
193 return true;
194 }
195
196
197 int retval = curl_easy_setopt(
198 curl_handle, CURLOPT_SSL_CTX_FUNCTION, CallbackSslCtx);
199 if (retval != CURLE_OK) {
200 LogCvmfs(kLogAuthz, kLogDebug, "cannot configure curl ssl callback");
201 return false;
202 }
203
204 UniquePtr<sslctx_info> parm(new sslctx_info);
205
206 STACK_OF(X509_INFO) *sk = NULL;
207 STACK_OF(X509) *certstack = sk_X509_new_null();
208 parm->chain = certstack;
209 if (certstack == NULL) {
210 LogCvmfs(kLogAuthz, kLogSyslogErr, "Failed to allocate new X509 chain.");
211 return false;
212 }
213
214 BIO *bio_token = BIO_new_mem_buf(token->data, token->size);
215 assert(bio_token != NULL);
216 sk = PEM_X509_INFO_read_bio(bio_token, NULL, NULL, NULL);
217 BIO_free(bio_token);
218 if (!sk) {
219 LogOpenSSLErrors("Failed to load credential file.");
220 sk_X509_INFO_free(sk);
221 sk_X509_free(certstack);
222 return false;
223 }
224
225 while (sk_X509_INFO_num(sk)) {
226 X509_INFO *xi = sk_X509_INFO_shift(sk);
227 if (xi == NULL) {
228 continue;
229 }
230 if (xi->x509 != NULL) {
231 #ifdef OPENSSL_API_INTERFACE_V11
232 retval = X509_up_ref(xi->x509);
233 assert(retval == 1);
234 #else
235 CRYPTO_add(&xi->x509->references, 1, CRYPTO_LOCK_X509);
236 #endif
237 sk_X509_push(certstack, xi->x509);
238 }
239 if ((xi->x_pkey != NULL) && (xi->x_pkey->dec_pkey != NULL)) {
240 parm->pkey = xi->x_pkey->dec_pkey;
241 #ifdef OPENSSL_API_INTERFACE_V11
242 retval = EVP_PKEY_up_ref(parm->pkey);
243 assert(retval == 1);
244 #else
245 CRYPTO_add(&parm->pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
246 #endif
247 }
248 X509_INFO_free(xi);
249 }
250 sk_X509_INFO_free(sk);
251
252 if (parm->pkey == NULL) {
253 // Sigh - PEM_X509_INFO_read doesn't understand old key encodings.
254 // Try a more general-purpose function.
255 BIO *bio_token = BIO_new_mem_buf(token->data, token->size);
256 assert(bio_token != NULL);
257 EVP_PKEY *old_pkey = PEM_read_bio_PrivateKey(bio_token, NULL, NULL, NULL);
258 BIO_free(bio_token);
259 if (old_pkey) {
260 parm->pkey = old_pkey;
261 } else {
262 sk_X509_free(certstack);
263 LogCvmfs(kLogAuthz, kLogSyslogErr,
264 "credential did not contain a decrypted private key.");
265 return false;
266 }
267 }
268
269 if (!sk_X509_num(certstack)) {
270 EVP_PKEY_free(parm->pkey);
271 sk_X509_free(certstack);
272 LogCvmfs(kLogAuthz, kLogSyslogErr,
273 "Credential file did not contain any actual credentials.");
274 return false;
275 } else {
276 LogCvmfs(kLogAuthz, kLogDebug, "Certificate stack contains %d entries.",
277 sk_X509_num(certstack));
278 }
279
280 AuthzToken *to_return = new AuthzToken();
281 to_return->type = kTokenX509;
282 to_return->data = static_cast<void *>(parm.Release());
283 curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA,
284 static_cast<sslctx_info *>(to_return->data));
285 *info_data = to_return;
286 return true;
287 }
288
289
290 void AuthzAttachment::LogOpenSSLErrors(const char *top_message) {
291 assert(ssl_strings_loaded_);
292 char error_buf[1024];
293 LogCvmfs(kLogAuthz, kLogSyslogWarn, "%s", top_message);
294 unsigned long next_err; // NOLINT; this is the type expected by OpenSSL
295 while ((next_err = ERR_get_error())) {
296 ERR_error_string_n(next_err, error_buf, 1024);
297 LogCvmfs(kLogAuthz, kLogSyslogErr, "%s", error_buf);
298 }
299 }
300
301
302 void AuthzAttachment::ReleaseCurlHandle(CURL *curl_handle, void *info_data) {
303 assert(info_data);
304
305 AuthzToken *token = static_cast<AuthzToken *>(info_data);
306 if (token->type == kTokenBearer) {
307 // Compiler complains if we delete a void*
308 bearer_info *bearer = static_cast<bearer_info *>(token->data);
309 delete static_cast<char *>(bearer->token);
310 curl_slist_free_all(bearer->list);
311 delete static_cast<bearer_info *>(token->data);
312 token->data = NULL;
313 delete token;
314
315 } else if (token->type == kTokenX509) {
316 sslctx_info *p = static_cast<sslctx_info *>(token->data);
317 STACK_OF(X509) *chain = p->chain;
318 EVP_PKEY *pkey = p->pkey;
319 p->chain = NULL;
320 p->pkey = NULL;
321 delete p;
322
323 // Calls X509_free on each element, then frees the stack itself
324 sk_X509_pop_free(chain, X509_free);
325 EVP_PKEY_free(pkey);
326
327 // Make sure that if CVMFS reuses this curl handle, curl doesn't try
328 // to reuse cert chain we just freed.
329 curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA, 0);
330 }
331 }
332