CernVM-FS  2.13.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
authz_curl.cc
Go to the documentation of this file.
1 
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 {
37  struct curl_slist *list;
38 
42  char *token;
43 };
44 } // anonymous namespace
45 
46 
48 
49 
51  : authz_session_manager_(sm) {
52  // Required for logging OpenSSL errors
53  SSL_load_error_strings();
54  ssl_strings_loaded_ = true;
55 }
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 
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 
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(
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);
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);
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) {
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 }
struct cvmcache_context * ctx
std::string membership_
Definition: authz_curl.h:44
virtual bool ConfigureCurlHandle(CURL *curl_handle, pid_t pid, void **info_data)
Definition: authz_curl.cc:149
void * data
Definition: authz.h:33
static CURLcode CallbackSslCtx(CURL *curl, void *sslctx, void *parm)
Definition: authz_curl.cc:58
assert((mem||(size==0))&&"Out Of Memory")
AuthzToken * GetTokenCopy(const pid_t pid, const std::string &membership)
static bool ssl_strings_loaded_
Definition: authz_curl.h:34
bool ConfigureSciTokenCurl(CURL *curl_handle, const AuthzToken &token, void **info_data)
Definition: authz_curl.cc:108
AuthzAttachment(AuthzSessionManager *sm)
Definition: authz_curl.cc:50
AuthzSessionManager * authz_session_manager_
Definition: authz_curl.h:39
static void LogOpenSSLErrors(const char *top_message)
Definition: authz_curl.cc:290
AuthzTokenType type
Definition: authz.h:32
unsigned size
Definition: authz.h:34
virtual void ReleaseCurlHandle(CURL *curl_handle, void *info_data)
Definition: authz_curl.cc:302
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:545