GCC Code Coverage Report


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