GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/authz/authz_curl.cc Lines: 5 152 3.3 %
Date: 2019-02-03 02:48:13 Branches: 0 63 0.0 %

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