GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_curl.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 5 162 3.1%
Branches: 1 183 0.5%

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