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  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  std::string auth_preamble = "Authorization: Bearer ";
134  std::string auth_header = auth_preamble + static_cast<char *>(bearer->token);
135  bearer->list = curl_slist_append(bearer->list, auth_header.c_str());
136  int retval = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, bearer->list);
137 
138  if (retval != CURLE_OK) {
139  LogCvmfs(kLogAuthz, kLogSyslogErr, "Failed to set Oauth2 Bearer Token");
140  return false;
141  }
142 
143  return true;
144 }
145 
146 
148  pid_t pid,
149  void **info_data) {
150  assert(info_data);
151 
152  // File catalog has no membership requirement, no tokens to attach
153  if (membership_.empty())
154  return false;
155 
156  // We cannot rely on libcurl to pipeline (yet), as cvmfs may
157  // bounce between different auth handles.
158  curl_easy_setopt(curl_handle, CURLOPT_FRESH_CONNECT, 1);
159  curl_easy_setopt(curl_handle, CURLOPT_FORBID_REUSE, 1);
160  curl_easy_setopt(curl_handle, CURLOPT_SSL_SESSIONID_CACHE, 0);
161 
162  UniquePtr<AuthzToken> token(
164  if (!token.IsValid()) {
165  LogCvmfs(kLogAuthz, kLogDebug, "failed to get authz token for pid %d", pid);
166  return false;
167  }
168 
169  switch (token->type) {
170  case kTokenBearer:
171  // If it's a scitoken, then just go to the private
172  // ConfigureSciTokenCurl function
173  return ConfigureSciTokenCurl(curl_handle, *token, info_data);
174 
175  case kTokenX509:
176  // The x509 code is below, so just break and go.
177  break;
178 
179  default:
180  // Oh no, don't know the the token type, throw error and return
181  LogCvmfs(kLogAuthz, kLogDebug, "unknown token type: %d", token->type);
182  return false;
183  }
184 
185  curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA, NULL);
186 
187  // The calling layer is reusing data;
188  if (*info_data) {
189  curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA,
190  static_cast<AuthzToken *>(*info_data)->data);
191  return true;
192  }
193 
194 
195  int retval = curl_easy_setopt(
196  curl_handle, CURLOPT_SSL_CTX_FUNCTION, CallbackSslCtx);
197  if (retval != CURLE_OK) {
198  LogCvmfs(kLogAuthz, kLogDebug, "cannot configure curl ssl callback");
199  return false;
200  }
201 
202  UniquePtr<sslctx_info> parm(new sslctx_info);
203 
204  STACK_OF(X509_INFO) *sk = NULL;
205  STACK_OF(X509) *certstack = sk_X509_new_null();
206  parm->chain = certstack;
207  if (certstack == NULL) {
208  LogCvmfs(kLogAuthz, kLogSyslogErr, "Failed to allocate new X509 chain.");
209  return false;
210  }
211 
212  BIO *bio_token = BIO_new_mem_buf(token->data, token->size);
213  assert(bio_token != NULL);
214  sk = PEM_X509_INFO_read_bio(bio_token, NULL, NULL, NULL);
215  BIO_free(bio_token);
216  if (!sk) {
217  LogOpenSSLErrors("Failed to load credential file.");
218  sk_X509_INFO_free(sk);
219  sk_X509_free(certstack);
220  return false;
221  }
222 
223  while (sk_X509_INFO_num(sk)) {
224  X509_INFO *xi = sk_X509_INFO_shift(sk);
225  if (xi == NULL) {
226  continue;
227  }
228  if (xi->x509 != NULL) {
229 #ifdef OPENSSL_API_INTERFACE_V11
230  retval = X509_up_ref(xi->x509);
231  assert(retval == 1);
232 #else
233  CRYPTO_add(&xi->x509->references, 1, CRYPTO_LOCK_X509);
234 #endif
235  sk_X509_push(certstack, xi->x509);
236  }
237  if ((xi->x_pkey != NULL) && (xi->x_pkey->dec_pkey != NULL)) {
238  parm->pkey = xi->x_pkey->dec_pkey;
239 #ifdef OPENSSL_API_INTERFACE_V11
240  retval = EVP_PKEY_up_ref(parm->pkey);
241  assert(retval == 1);
242 #else
243  CRYPTO_add(&parm->pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
244 #endif
245  }
246  X509_INFO_free(xi);
247  }
248  sk_X509_INFO_free(sk);
249 
250  if (parm->pkey == NULL) {
251  // Sigh - PEM_X509_INFO_read doesn't understand old key encodings.
252  // Try a more general-purpose function.
253  BIO *bio_token = BIO_new_mem_buf(token->data, token->size);
254  assert(bio_token != NULL);
255  EVP_PKEY *old_pkey = PEM_read_bio_PrivateKey(bio_token, NULL, NULL, NULL);
256  BIO_free(bio_token);
257  if (old_pkey) {
258  parm->pkey = old_pkey;
259  } else {
260  sk_X509_free(certstack);
262  "credential did not contain a decrypted private key.");
263  return false;
264  }
265  }
266 
267  if (!sk_X509_num(certstack)) {
268  EVP_PKEY_free(parm->pkey);
269  sk_X509_free(certstack);
271  "Credential file did not contain any actual credentials.");
272  return false;
273  } else {
274  LogCvmfs(kLogAuthz, kLogDebug, "Certificate stack contains %d entries.",
275  sk_X509_num(certstack));
276  }
277 
278  AuthzToken *to_return = new AuthzToken();
279  to_return->type = kTokenX509;
280  to_return->data = static_cast<void *>(parm.Release());
281  curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA,
282  static_cast<sslctx_info *>(to_return->data));
283  *info_data = to_return;
284  return true;
285 }
286 
287 
288 void AuthzAttachment::LogOpenSSLErrors(const char *top_message) {
290  char error_buf[1024];
291  LogCvmfs(kLogAuthz, kLogSyslogWarn, "%s", top_message);
292  unsigned long next_err; // NOLINT; this is the type expected by OpenSSL
293  while ((next_err = ERR_get_error())) {
294  ERR_error_string_n(next_err, error_buf, 1024);
295  LogCvmfs(kLogAuthz, kLogSyslogErr, "%s", error_buf);
296  }
297 }
298 
299 
300 void AuthzAttachment::ReleaseCurlHandle(CURL *curl_handle, void *info_data) {
301  assert(info_data);
302 
303  AuthzToken *token = static_cast<AuthzToken *>(info_data);
304  if (token->type == kTokenBearer) {
305  // Compiler complains if we delete a void*
306  bearer_info *bearer = static_cast<bearer_info *>(token->data);
307  delete static_cast<char *>(bearer->token);
308  curl_slist_free_all(bearer->list);
309  delete static_cast<bearer_info *>(token->data);
310  token->data = NULL;
311  delete token;
312 
313  } else if (token->type == kTokenX509) {
314  sslctx_info *p = static_cast<sslctx_info *>(token->data);
315  STACK_OF(X509) *chain = p->chain;
316  EVP_PKEY *pkey = p->pkey;
317  p->chain = NULL;
318  p->pkey = NULL;
319  delete p;
320 
321  // Calls X509_free on each element, then frees the stack itself
322  sk_X509_pop_free(chain, X509_free);
323  EVP_PKEY_free(pkey);
324 
325  // Make sure that if CVMFS reuses this curl handle, curl doesn't try
326  // to reuse cert chain we just freed.
327  curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA, 0);
328  }
329 }
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:147
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:288
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:300
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:545