GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_curl.cc
Date: 2026-05-19 11:45:12
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 #include "authz_curl.h"
5
6 #include <openssl/err.h>
7 #include <openssl/ssl.h>
8 #include <pthread.h>
9
10 #include <cassert>
11
12 #include "authz/authz_session.h"
13 #include "crypto/openssl_version.h"
14 #include "duplex_curl.h"
15 #include "util/concurrency.h"
16 #include "util/logging.h"
17 #include "util/pointer.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 529 AuthzAttachment::AuthzAttachment(AuthzSessionManager *sm)
50 529 : authz_session_manager_(sm) {
51 // Required for logging OpenSSL errors
52
1/2
✓ Branch 1 taken 529 times.
✗ Branch 2 not taken.
529 SSL_load_error_strings();
53 529 ssl_strings_loaded_ = true;
54 529 }
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 UniquePtr<AuthzToken> token(
164 authz_session_manager_->GetTokenCopy(pid, membership_));
165 if (!token.IsValid()) {
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 UniquePtr<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