GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/whitelist.cc Lines: 122 196 62.2 %
Date: 2019-02-03 02:48:13 Branches: 54 114 47.4 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 */
4
5
#include "cvmfs_config.h"
6
#include "whitelist.h"
7
8
#include <algorithm>
9
#include <cassert>
10
#include <cstring>
11
#include <ctime>
12
13
#include "download.h"
14
#include "logging.h"
15
#include "signature.h"
16
#include "smalloc.h"
17
#include "util/string.h"
18
19
using namespace std;  // NOLINT
20
21
namespace whitelist {
22
23
const int Whitelist::kFlagVerifyRsa     = 0x01;
24
const int Whitelist::kFlagVerifyPkcs7   = 0x02;
25
const int Whitelist::kFlagVerifyCaChain = 0x04;
26
27
28
26
void Whitelist::CopyBuffers(unsigned *plain_size, unsigned char **plain_buf,
29
                            unsigned *pkcs7_size, unsigned char **pkcs7_buf)
30
  const
31
{
32
26
  *plain_size = plain_size_;
33
26
  *pkcs7_size = pkcs7_size_;
34
26
  *plain_buf = NULL;
35
26
  *pkcs7_buf = NULL;
36
26
  if (plain_size_ > 0) {
37
26
    *plain_buf = reinterpret_cast<unsigned char *>(smalloc(plain_size_));
38
26
    memcpy(*plain_buf, plain_buf_, plain_size_);
39
  }
40
26
  if (pkcs7_size_ > 0) {
41
    *pkcs7_buf = reinterpret_cast<unsigned char *>(smalloc(pkcs7_size_));
42
    memcpy(*pkcs7_buf, pkcs7_buf_, pkcs7_size_);
43
  }
44
26
}
45
46
47
time_t Whitelist::expires() {
48
  assert(status_ == kStAvailable);
49
  return expires_;
50
}
51
52
53
bool Whitelist::IsExpired() const {
54
  assert(status_ == kStAvailable);
55
  return time(NULL) > expires_;
56
}
57
58
59
32
Failures Whitelist::VerifyLoadedCertificate() const {
60
32
  assert(status_ == kStAvailable);
61
62
32
  vector<string> blacklist = signature_manager_->GetBlacklist();
63
35
  for (unsigned i = 0; i < blacklist.size(); ++i) {
64
    shash::Any this_hash =
65
9
      signature::SignatureManager::MkFromFingerprint(blacklist[i]);
66
9
    if (this_hash.IsNull())
67
3
      continue;
68
69
6
    shash::Algorithms algorithm = this_hash.algorithm;
70
6
    if (this_hash == signature_manager_->HashCertificate(algorithm))
71
6
      return kFailBlacklisted;
72
  }
73
74
26
  for (unsigned i = 0; i < fingerprints_.size(); ++i) {
75
26
    shash::Algorithms algorithm = fingerprints_[i].algorithm;
76
26
    if (signature_manager_->HashCertificate(algorithm) == fingerprints_[i]) {
77
26
      if (verification_flags_ & kFlagVerifyCaChain) {
78
        bool retval = signature_manager_->VerifyCaChain();
79
        if (!retval)
80
          return kFailBadCaChain;
81
      }
82
26
      return kFailOk;
83
    }
84
  }
85
  return kFailNotListed;
86
}
87
88
89
32
Failures Whitelist::Load(const std::string &base_url) {
90
32
  const bool probe_hosts = base_url == "";
91
  bool retval_b;
92
  download::Failures retval_dl;
93
  whitelist::Failures retval_wl;
94
95
32
  Reset();
96
97
32
  const string whitelist_url = base_url + string("/.cvmfswhitelist");
98
  download::JobInfo download_whitelist(&whitelist_url,
99
32
                                       false, probe_hosts, NULL);
100
32
  retval_dl = download_manager_->Fetch(&download_whitelist);
101
32
  if (retval_dl != download::kFailOk)
102
    return kFailLoad;
103
32
  plain_size_ = download_whitelist.destination_mem.pos;
104
32
  if (plain_size_ == 0)
105
    return kFailEmpty;
106
  plain_buf_ =
107
32
    reinterpret_cast<unsigned char *>(download_whitelist.destination_mem.data);
108
109
32
  retval_wl = ParseWhitelist(plain_buf_, plain_size_);
110
32
  if (retval_wl != kFailOk)
111
    return retval_wl;
112
113
32
  if (verification_flags_ & kFlagVerifyRsa) {
114
32
    retval_b = signature_manager_->VerifyLetter(plain_buf_, plain_size_, true);
115
32
    if (!retval_b) {
116
      LogCvmfs(kLogCvmfs, kLogDebug, "failed to verify repository whitelist");
117
      return kFailBadSignature;
118
    }
119
  }
120
121
32
  if (verification_flags_ & kFlagVerifyPkcs7) {
122
    // Load the separate whitelist pkcs7 structure
123
    const string whitelist_pkcs7_url =
124
      base_url + string("cvmfswhitelist.pkcs7");
125
    download::JobInfo download_whitelist_pkcs7(&whitelist_pkcs7_url, false,
126
                                               probe_hosts, NULL);
127
    retval_dl = download_manager_->Fetch(&download_whitelist_pkcs7);
128
    if (retval_dl != download::kFailOk)
129
      return kFailLoadPkcs7;
130
    pkcs7_size_ = download_whitelist_pkcs7.destination_mem.pos;
131
    if (pkcs7_size_ == 0)
132
      return kFailEmptyPkcs7;
133
    pkcs7_buf_ = reinterpret_cast<unsigned char *>
134
      (download_whitelist_pkcs7.destination_mem.data);
135
136
    unsigned char *extracted_whitelist;
137
    unsigned extracted_whitelist_size;
138
    vector<string> alt_uris;
139
    retval_b =
140
      signature_manager_->VerifyPkcs7(pkcs7_buf_, pkcs7_size_,
141
                                      &extracted_whitelist,
142
                                      &extracted_whitelist_size,
143
                                      &alt_uris);
144
    if (!retval_b) {
145
      LogCvmfs(kLogCvmfs, kLogDebug,
146
               "failed to verify repository whitelist (pkcs#7): %s",
147
               signature_manager_->GetCryptoError().c_str());
148
      return kFailBadPkcs7;
149
    }
150
151
    // Check for subject alternative name matching the repository name
152
    bool found_uri = false;
153
    for (unsigned i = 0; i < alt_uris.size(); ++i) {
154
      LogCvmfs(kLogSignature, kLogDebug, "found pkcs#7 signer uri %s",
155
               alt_uris[i].c_str());
156
      if (alt_uris[i] == "cvmfs:" + fqrn_) {
157
        found_uri = true;
158
        break;
159
      }
160
    }
161
    if (!found_uri) {
162
      LogCvmfs(kLogCvmfs, kLogDebug,
163
               "failed to find whitelist signer with SAN/URI cvmfs:%s",
164
               fqrn_.c_str());
165
      free(extracted_whitelist);
166
      return kFailBadSignaturePkcs7;
167
    }
168
169
    // Check once again the extracted whitelist
170
    Reset();
171
    LogCvmfs(kLogCvmfs, kLogDebug, "Extracted pkcs#7 whitelist:\n%s",
172
             string(reinterpret_cast<char *>(extracted_whitelist),
173
                    extracted_whitelist_size).c_str());
174
    retval_wl = ParseWhitelist(extracted_whitelist, extracted_whitelist_size);
175
    if (retval_wl != kFailOk) {
176
      LogCvmfs(kLogCvmfs, kLogDebug,
177
               "failed to verify repository certificate against pkcs#7 "
178
               "whitelist");
179
      return kFailMalformedPkcs7;
180
    }
181
  }
182
183
32
  status_ = kStAvailable;
184
32
  return kFailOk;
185
}
186
187
188
/**
189
 * Helps for the time being with whitelists valid until after Y2038 on 32 bit
190
 * systems.
191
 */
192
36
bool Whitelist::IsBefore(time_t now, const struct tm &t_whitelist) {
193
  struct tm t_local;
194
36
  if (gmtime_r(&now, &t_local) == NULL)
195
    return false;
196
36
  if (t_local.tm_year < t_whitelist.tm_year) return true;
197
11
  if (t_local.tm_year > t_whitelist.tm_year) return false;
198
10
  if (t_local.tm_mon < t_whitelist.tm_mon) return true;
199
3
  if (t_local.tm_mon > t_whitelist.tm_mon) return false;
200
3
  if (t_local.tm_mday < t_whitelist.tm_mday) return true;
201
3
  if (t_local.tm_mday > t_whitelist.tm_mday) return false;
202
3
  if (t_local.tm_hour < t_whitelist.tm_hour) return true;
203
1
  return false;
204
}
205
206
207
38
Failures Whitelist::ParseWhitelist(const unsigned char *whitelist,
208
                                   const unsigned whitelist_size)
209
{
210
38
  time_t local_timestamp = time(NULL);
211
38
  string line;
212
38
  unsigned payload_bytes = 0;
213
38
  bool verify_pkcs7 = false;
214
38
  bool verify_cachain = false;
215
216
  // Check timestamp (UTC), ignore issue date (legacy)
217
38
  line = GetLineMem(reinterpret_cast<const char *>(whitelist), whitelist_size);
218
38
  if (line.length() != 14) {
219
1
    LogCvmfs(kLogSignature, kLogDebug, "invalid timestamp format");
220
1
    return kFailMalformed;
221
  }
222
37
  payload_bytes += 15;
223
224
  // Expiry date
225
  line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes,
226
37
                    whitelist_size-payload_bytes);
227
37
  if (line.length() != 15) {
228
1
    LogCvmfs(kLogSignature, kLogDebug, "invalid timestamp format");
229
1
    return kFailMalformed;
230
  }
231
  struct tm tm_wl;
232
36
  memset(&tm_wl, 0, sizeof(struct tm));
233
36
  tm_wl.tm_year = String2Int64(line.substr(1, 4))-1900;
234
36
  tm_wl.tm_mon = String2Int64(line.substr(5, 2)) - 1;
235
36
  tm_wl.tm_mday = String2Int64(line.substr(7, 2));
236
36
  tm_wl.tm_hour = String2Int64(line.substr(9, 2));
237
36
  tm_wl.tm_min = tm_wl.tm_sec = 0;  // exact on hours level
238
36
  time_t timestamp = timegm(&tm_wl);
239
  LogCvmfs(kLogSignature, kLogDebug,
240
           "whitelist UTC expiry timestamp in localtime: %s",
241
36
           StringifyTime(timestamp, false).c_str());
242
  LogCvmfs(kLogSignature, kLogDebug,  "local time: %s",
243
36
          StringifyTime(local_timestamp, true).c_str());
244
  // Makeshift solution to deal with whitelists valid after Y2038 on 32bit
245
  // machines.  Still unclear how glibc is going to treat the problem.
246
36
  if (!IsBefore(local_timestamp, tm_wl)) {
247
    LogCvmfs(kLogSignature, kLogDebug | kLogSyslogErr,
248
2
             "whitelist lifetime verification failed, expired");
249
2
    return kFailExpired;
250
  }
251
  // if (timestamp < 0) {
252
  //   LogCvmfs(kLogSignature, kLogDebug, "invalid timestamp");
253
  //   return kFailMalformed;
254
  // }
255
  // if (local_timestamp > timestamp) {
256
  //   LogCvmfs(kLogSignature, kLogDebug | kLogSyslogErr,
257
  //            "whitelist lifetime verification failed, expired");
258
  //   return kFailExpired;
259
  // }
260
34
  expires_ = timestamp;
261
34
  payload_bytes += 16;
262
263
  // Check repository name
264
  line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes,
265
34
                    whitelist_size-payload_bytes);
266


34
  if ((fqrn_ != "") && ("N" + fqrn_ != line)) {
267
    LogCvmfs(kLogSignature, kLogDebug,
268
             "repository name on the whitelist does not match "
269
             "(found %s, expected %s)",
270
1
             line.c_str(), fqrn_.c_str());
271
1
    return kFailNameMismatch;
272
  }
273
33
  payload_bytes += line.length() + 1;
274
275
  // Check for PKCS7
276
  line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes,
277
33
                    whitelist_size-payload_bytes);
278
33
  if (line == "Vpkcs7") {
279
    LogCvmfs(kLogSignature, kLogDebug, "whitelist verification: pkcs#7");
280
    verify_pkcs7 = true;
281
    payload_bytes += line.length() + 1;
282
    line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes,
283
                      whitelist_size-payload_bytes);
284
  }
285
286
  // Check for CA chain verification
287
  line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes,
288
33
                    whitelist_size-payload_bytes);
289
33
  if (line == "Wcachain") {
290
    LogCvmfs(kLogSignature, kLogDebug,
291
             "whitelist imposes ca chain verification of manifest signature");
292
    verify_cachain = true;
293
    payload_bytes += line.length() + 1;
294
    line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes,
295
                      whitelist_size-payload_bytes);
296
  }
297
298
33
  do {
299
65
    if (line == "--") break;
300
33
    shash::Any this_hash = signature::SignatureManager::MkFromFingerprint(line);
301
33
    if (!this_hash.IsNull())
302
32
      fingerprints_.push_back(this_hash);
303
304
33
    payload_bytes += line.length() + 1;
305
    line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes,
306
33
                      whitelist_size-payload_bytes);
307
  } while (payload_bytes < whitelist_size);
308
309
33
  verification_flags_ = verify_pkcs7 ? kFlagVerifyPkcs7 : kFlagVerifyRsa;
310
33
  if (verify_cachain)
311
    verification_flags_ |= kFlagVerifyCaChain;
312
33
  return kFailOk;
313
}
314
315
316
111
void Whitelist::Reset() {
317
111
  status_ = kStNone;
318
111
  fingerprints_.clear();
319
111
  expires_ = 0;
320
111
  verification_flags_ = 0;
321
111
  if (plain_buf_)
322
32
    free(plain_buf_);
323
111
  if (pkcs7_buf_)
324
    free(pkcs7_buf_);
325
111
  plain_buf_ = NULL;
326
111
  pkcs7_buf_ = NULL;
327
111
  plain_size_ = 0;
328
111
  pkcs7_size_ = 0;
329
111
}
330
331
332
39
Whitelist::Whitelist(const string &fqrn,
333
                     download::DownloadManager *download_manager,
334
                     signature::SignatureManager *signature_manager) :
335
  fqrn_(fqrn),
336
  download_manager_(download_manager),
337
  signature_manager_(signature_manager),
338
  plain_buf_(NULL),
339
  plain_size_(0),
340
  pkcs7_buf_(NULL),
341
39
  pkcs7_size_(0)
342
{
343
39
  Reset();
344
}
345
346
347
Whitelist::Whitelist(const Whitelist &other) :
348
  fqrn_(other.fqrn_),
349
  download_manager_(other.download_manager_),
350
  signature_manager_(other.signature_manager_),
351
  status_(other.status_),
352
  fingerprints_(other.fingerprints_),
353
  expires_(other.expires_),
354
  verification_flags_(other.verification_flags_)
355
{
356
  other.CopyBuffers(&plain_size_, &plain_buf_, &pkcs7_size_, &pkcs7_buf_);
357
}
358
359
360
// Testing only
361
1
Whitelist::Whitelist()
362
  : download_manager_(NULL)
363
  , signature_manager_(NULL)
364
  , status_(kStNone)
365
  , expires_(0)
366
  , verification_flags_(0)
367
  , plain_buf_(NULL)
368
  , plain_size_(0)
369
  , pkcs7_buf_(NULL)
370
1
  , pkcs7_size_(0)
371
{
372
1
}
373
374
Whitelist &Whitelist::operator= (const Whitelist &other) {
375
  if (&other == this)
376
    return *this;
377
378
  Reset();
379
  fqrn_ = other.fqrn_;
380
  download_manager_ = other.download_manager_;
381
  signature_manager_ = other.signature_manager_;
382
  status_ = other.status_;
383
  fingerprints_ = other.fingerprints_;
384
  expires_ = other.expires_;
385
  verification_flags_ = other.verification_flags_;
386
  other.CopyBuffers(&plain_size_, &plain_buf_, &pkcs7_size_, &pkcs7_buf_);
387
388
  return *this;
389
}
390
391
392
40
Whitelist::~Whitelist() {
393
40
  Reset();
394
}
395
396
}  // namespace whitelist