GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/webapi/macaroon.cc Lines: 229 267 85.8 %
Date: 2018-10-07 00:43:30 Branches: 132 254 52.0 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System
3
 */
4
5
#include "cvmfs_config.h"
6
#include "macaroon.h"
7
8
#include <ctime>
9
10
#include "encrypt.h"
11
#include "hash.h"
12
#include "sanitizer.h"
13
#include "util/pointer.h"
14
#include "util/string.h"
15
#include "uuid.h"
16
17
using namespace std;  // NOLINT
18
19
28
Macaroon::Macaroon()
20
  : secret_key_storage_(NULL)
21
  , secret_key_publisher_(NULL)
22
  , expiry_utc_(0)
23
  , key_onetime_(NULL)
24
  , publish_operation_(kPublishNoop)
25
28
  , expiry_utc_operation_(0)
26
{
27
28
  hmac_primary_.algorithm = shash::kSha1;
28
28
  hmac_3rd_party_.algorithm = shash::kSha1;
29
28
  payload_hash_.algorithm = shash::kShake128;
30
28
}
31
32
33
/**
34
 * Used on the lease server where both keys are known.
35
 */
36
28
Macaroon::Macaroon(
37
  const string &key_id_storage,
38
  const cipher::Key *secret_key_storage,
39
  const string &key_id_publisher,
40
  const cipher::Key *secret_key_publisher)
41
  : random_nonce_(cvmfs::Uuid::CreateOneTime())
42
  , key_id_storage_(key_id_storage)
43
  , key_id_publisher_(key_id_publisher)
44
  , secret_key_storage_(secret_key_storage)
45
  , secret_key_publisher_(secret_key_publisher)
46
  , expiry_utc_(0)
47
  , key_onetime_(NULL)
48
  , publish_operation_(kPublishNoop)
49
28
  , expiry_utc_operation_(0)
50
{
51
28
  hmac_primary_.algorithm = shash::kSha1;
52
53
  UniquePtr<cipher::Cipher> cipher_aes(
54
28
    cipher::Cipher::Create(cipher::kAes256Cbc));
55
28
  assert(cipher_aes.IsValid());
56
28
  key_onetime_ = cipher::Key::CreateRandomly(cipher_aes->key_size());
57
28
  assert(key_onetime_ != NULL);
58
59
28
  string cipher_text;
60
  bool retval = cipher_aes->Encrypt(
61
28
    key_onetime_->ToBase64(), *secret_key_storage_, &cipher_text);
62
28
  assert(retval);
63
28
  key_onetime4storage_ = Base64(cipher_text);
64
  retval = cipher_aes->Encrypt(
65
28
    key_onetime_->ToBase64(), *secret_key_publisher_, &cipher_text);
66
28
  assert(retval);
67
28
  key_onetime4publisher_ = Base64(cipher_text);
68
}
69
70
71
56
Macaroon::~Macaroon() {
72
56
  delete key_onetime_;
73
}
74
75
76
/**
77
 * Creates the HMAC chain for the 3rd party caveats of the macaroon.  Uses the
78
 * one time key as a starting point of the HMAC chain.
79
 */
80
28
void Macaroon::Compute3rdPartyHmac() {
81
28
  assert(key_onetime_ != NULL);
82
83
  // Start with a base64 version of the secret key and the random nonce of the
84
  // primary macaroon, thereby binding the two together
85
28
  HmacString(key_onetime_->ToBase64(), random_nonce_, &hmac_3rd_party_);
86
87
  // Always the latest hmac_3rd_party_.ToString()
88
28
  string key_walker;
89
90
28
  key_walker = hmac_3rd_party_.ToString();
91
28
  int operation = publish_operation_;
92
28
  HmacString(key_walker, StringifyInt(operation), &hmac_3rd_party_);
93
94
28
  key_walker = hmac_3rd_party_.ToString();
95
28
  HmacString(key_walker, StringifyInt(expiry_utc_operation_), &hmac_3rd_party_);
96
97
28
  if (!payload_hash_.IsNull()) {
98
    key_walker = hmac_3rd_party_.ToString();
99
    HmacString(key_walker, payload_hash_.ToString(), &hmac_3rd_party_);
100
  }
101
102
  // Seal the HMAC
103
28
  key_walker = hmac_3rd_party_.ToString();
104
28
  HashString(key_walker, &hmac_3rd_party_);
105
28
}
106
107
108
/**
109
 * Creates the HMAC chain for the macaroon.  Computation from scratch can only
110
 * be done by the origin service and the taget service because only they possess
111
 * the secret key that starts the chain.
112
 */
113
56
void Macaroon::ComputePrimaryHmac() {
114
56
  assert(secret_key_storage_ != NULL);
115
116
  // Start with a base64 version of the secret key and the random nonce
117
56
  HmacString(secret_key_storage_->ToBase64(), random_nonce_, &hmac_primary_);
118
119
  // Always the latest hmac_.ToString()
120
56
  string key_walker;
121
122
56
  key_walker = hmac_primary_.ToString();
123
56
  HmacString(key_walker, key_id_storage_, &hmac_primary_);
124
125
56
  key_walker = hmac_primary_.ToString();
126
56
  HmacString(key_walker, key_id_publisher_, &hmac_primary_);
127
128
56
  key_walker = hmac_primary_.ToString();
129
56
  HmacString(key_walker, origin_, &hmac_primary_);
130
131
56
  key_walker = hmac_primary_.ToString();
132
56
  HmacString(key_walker, target_hint_, &hmac_primary_);
133
134
56
  key_walker = hmac_primary_.ToString();
135
56
  HmacString(key_walker, StringifyInt(expiry_utc_), &hmac_primary_);
136
137
56
  key_walker = hmac_primary_.ToString();
138
56
  HmacString(key_walker, fqrn_, &hmac_primary_);
139
140
56
  key_walker = hmac_primary_.ToString();
141
56
  HmacString(key_walker, root_path_, &hmac_primary_);
142
143
56
  key_walker = hmac_primary_.ToString();
144
56
  HmacString(key_walker, publisher_hint_, &hmac_primary_);
145
146
56
  key_walker = hmac_primary_.ToString();
147
56
  HmacString(key_walker, key_onetime4storage_, &hmac_primary_);
148
149
56
  key_walker = hmac_primary_.ToString();
150
56
  HmacString(key_walker, key_onetime4publisher_, &hmac_primary_);
151
152
  // Seal the HMAC
153
56
  key_walker = hmac_primary_.ToString();
154
56
  HashString(key_walker, &hmac_primary_);
155
56
}
156
157
158
14
bool Macaroon::Create3rdPartyFromJson(JSON *json) {
159
14
  JSON *walker = json;
160

14
  while (walker != NULL) {
161
42
    if (walker->name == NULL)
162
      return false;
163
42
    string name = walker->name;
164
42
    json_type type = walker->type;
165
166
    // Known field types
167
42
    if (name == "publish_operation") {
168
14
      if (type != JSON_INT) return false;
169
14
      if (walker->int_value < kPublishTransaction) return false;
170
14
      if (walker->int_value > kPublishCommit) return false;
171
14
      publish_operation_ = PublishOperation(walker->int_value);
172
28
    } else if (name == "expiry_utc_operation") {
173
14
      if (type != JSON_INT) return false;
174
14
      if (walker->int_value < 0) return false;
175
14
      expiry_utc_operation_ = walker->int_value;
176
14
    } else if (name == "payload_hash") {
177
      if (type != JSON_STRING) return false;
178
      if (walker->string_value == NULL) return false;
179
      payload_hash_ =
180
        shash::MkFromHexPtr(shash::HexPtr(string(walker->string_value)));
181
14
    } else if (name == "hmac") {
182
14
      if (type != JSON_STRING) return false;
183
14
      if (walker->string_value == NULL) return false;
184
      hmac_3rd_party_ =
185
14
        shash::MkFromHexPtr(shash::HexPtr(string(walker->string_value)));
186
    }
187
188
42
    walker = walker->next_sibling;
189
  }
190
14
  return true;
191
}
192
193
194
28
bool Macaroon::CreateFromJson(JSON *json) {
195
  // Search "cvmfs_macaroon_v1 sub structure"
196
28
  JSON *walker = json;
197
56
  while (walker) {
198



28
    if ((string(walker->name) == "cvmfs_macaroon_v1") &&
199
        (walker->type == JSON_OBJECT))
200
    {
201
28
      walker = walker->first_child;
202
28
      break;
203
    }
204
    walker = walker->next_sibling;
205
  }
206
28
  if (walker == NULL)
207
    return false;
208
209
  // We are now in the cvmfs_macaroon_v1 sub structure
210
28
  sanitizer::UuidSanitizer uuid_sanitizer;
211
28
  sanitizer::UriSanitizer uri_sanitizer;
212
28
  sanitizer::RepositorySanitizer repo_sanitizer;
213
28
  sanitizer::Base64Sanitizer base64_sanitizer;
214
350
  do {
215
350
    if (walker->name == NULL)
216
      return false;
217
350
    string name = walker->name;
218
350
    json_type type = walker->type;
219
220
    // Known field types
221
350
    if (name == "random_nonce") {
222
28
      if (type != JSON_STRING) return false;
223
28
      if (walker->string_value == NULL) return false;
224
28
      if (!uuid_sanitizer.IsValid(walker->string_value)) return false;
225
28
      random_nonce_ = walker->string_value;
226
322
    } else if (name == "key_id_storage") {
227
28
      if (type != JSON_STRING) return false;
228
28
      if (walker->string_value == NULL) return false;
229
28
      if (!uri_sanitizer.IsValid(walker->string_value)) return false;
230
28
      key_id_storage_ = walker->string_value;
231
294
    } else if (name == "key_id_storage") {
232
      if (type != JSON_STRING) return false;
233
      if (walker->string_value == NULL) return false;
234
      if (!uri_sanitizer.IsValid(walker->string_value)) return false;
235
      key_id_storage_ = walker->string_value;
236
294
    } else if (name == "key_id_publisher") {
237
28
      if (type != JSON_STRING) return false;
238
28
      if (walker->string_value == NULL) return false;
239
28
      if (!uri_sanitizer.IsValid(walker->string_value)) return false;
240
28
      key_id_publisher_ = walker->string_value;
241
266
    } else if (name == "origin") {
242
28
      if (type != JSON_STRING) return false;
243
28
      if (walker->string_value == NULL) return false;
244
28
      if (!uri_sanitizer.IsValid(walker->string_value)) return false;
245
28
      origin_ = walker->string_value;
246
238
    } else if (name == "target_hint") {
247
28
      if (type != JSON_STRING) return false;
248
28
      if (walker->string_value == NULL) return false;
249
28
      if (!uri_sanitizer.IsValid(walker->string_value)) return false;
250
28
      target_hint_ = walker->string_value;
251
210
    } else if (name == "expiry_utc") {
252
28
      if (type != JSON_INT) return false;
253
28
      if (walker->int_value < 0) return false;
254
28
      expiry_utc_ = walker->int_value;
255
182
    } else if (name == "fqrn") {
256
28
      if (type != JSON_STRING) return false;
257
28
      if (walker->string_value == NULL) return false;
258
28
      if (!repo_sanitizer.IsValid(walker->string_value)) return false;
259
28
      fqrn_ = walker->string_value;
260
154
    } else if (name == "root_path") {
261
28
      if (type != JSON_STRING) return false;
262
28
      if (walker->name == NULL) return false;
263
28
      root_path_ = walker->string_value;
264
126
    } else if (name == "publisher_hint") {
265
28
      if (type != JSON_STRING) return false;
266
28
      if (walker->string_value == NULL) return false;
267
28
      if (!uri_sanitizer.IsValid(walker->string_value)) return false;
268
28
      publisher_hint_ = walker->string_value;
269
98
    } else if (name == "vid") {
270
28
      if (type != JSON_STRING) return false;
271
28
      if (walker->string_value == NULL) return false;
272
28
      if (!base64_sanitizer.IsValid(walker->string_value)) return false;
273
28
      key_onetime4storage_ = walker->string_value;
274
70
    } else if (name == "cid") {
275
28
      if (type != JSON_STRING) return false;
276
28
      if (walker->string_value == NULL) return false;
277
28
      if (!base64_sanitizer.IsValid(walker->string_value)) return false;
278
28
      key_onetime4publisher_ = walker->string_value;
279
42
    } else if (name == "hmac") {
280
28
      if (type != JSON_STRING) return false;
281
28
      if (walker->string_value == NULL) return false;
282
      hmac_primary_ =
283
28
        shash::MkFromHexPtr(shash::HexPtr(string(walker->string_value)));
284

14
    } else if ((name == "publisher_caveats") && (type == JSON_OBJECT)) {
285
14
      walker = walker->first_child;
286
14
      if (!Create3rdPartyFromJson(walker)) return false;
287
14
      walker = walker->parent;
288
    }
289
290
350
    walker = walker->next_sibling;
291
  } while (walker);
292
293
28
  return true;
294
}
295
296
297
/**
298
 * Used on the release manager machine.  The primary macaroon is taken as is,
299
 * only the 3rd party caveats are added.
300
 */
301
14
std::string Macaroon::ExportAttenuated() {
302
14
  Compute3rdPartyHmac();
303
  string json_text = string("{\"cvmfs_macaroon_v1\":{") +
304
    ExportPrimaryFields() +
305
    ",\"hmac\":\"" + hmac_primary_.ToString() + "\"" +
306
    "\"publisher_caveats\":{" +
307
    Export3rdPartyFields() +
308
    ",\"hmac\":\"" + hmac_3rd_party_.ToString() + "\"" +
309
14
    "}}}";
310
311
14
  UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_text));
312
14
  assert(json_document.IsValid());
313
14
  return json_document->PrintCanonical();
314
}
315
316
317
/**
318
 * The order of the JSON fields matters because the HMAC is constructed
319
 * field-by-field.  Requires knowledge of the secret storage key.  Used by the
320
 * lease server.
321
 */
322
42
string Macaroon::ExportPrimary() {
323
42
  ComputePrimaryHmac();
324
  string json_text = string("{\"cvmfs_macaroon_v1\":{") +
325
    ExportPrimaryFields() +
326
    ",\"hmac\":\"" + hmac_primary_.ToString() + "\"" +
327
42
    "}}";
328
42
  UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_text));
329
42
  assert(json_document.IsValid());
330
42
  return json_document->PrintCanonical();
331
}
332
333
334
/**
335
 * Export the inner guts of the 3rd party caveats without hmac.
336
 */
337
14
std::string Macaroon::Export3rdPartyFields() {
338
  return "\"publish_operation\":" + StringifyInt(publish_operation_) +
339
    ",\"expiry_utc_operation\":" + StringifyInt(expiry_utc_operation_) +
340
    (payload_hash_.IsNull()
341
      ? ""
342



14
      : ("\"payload_hash\":\"" + payload_hash_.ToString() + "\""));
343
}
344
345
346
347
/**
348
 * Export the inner guts of the primary macaroon without hmac.
349
 */
350
56
std::string Macaroon::ExportPrimaryFields() {
351
  return "\"random_nonce\":\"" + random_nonce_ + "\"" +
352
    "\"key_id_storage\":\"" + key_id_storage_ + "\"" +
353
    "\"key_id_publisher\":\"" + key_id_publisher_ + "\"" +
354
    ",\"origin\":\"" + origin_ + "\"" +
355
    ",\"target_hint\":\"" + target_hint_ + "\"" +
356
    ",\"expiry_utc\":" + StringifyInt(expiry_utc_) +
357
    ",\"fqrn\":\"" + fqrn_ + "\"" +
358
    ",\"root_path\":\"" + root_path_ + "\"" +
359
    ",\"publisher_hint\":\"" + publisher_hint_ + "\"" +
360
    ",\"vid\":\"" + key_onetime4storage_ + "\"" +
361
56
    ",\"cid\":\"" + key_onetime4publisher_ + "\"";
362
}
363
364
365
/**
366
 * The secret_key is either the publisher's one or the storage's one, depending
367
 * on where the method is called.
368
 */
369
28
Macaroon::VerifyFailures Macaroon::ExtractOnetimeKey(
370
  const string &key_onetime4me,
371
  const cipher::Key &my_secret_key)
372
{
373
28
  string ciphertext;
374
28
  string plaintext;
375
28
  string key_raw;
376
28
  bool retval = Debase64(key_onetime4me, &ciphertext);
377
28
  if (!retval) {
378
    return kFailBadMacaroon;
379
  }
380
  retval = cipher::Cipher::Decrypt(
381
28
    ciphertext, my_secret_key, &plaintext);
382

28
  if (!retval || (plaintext == "")) {
383
    return kFailDecrypt;
384
  }
385
28
  retval = Debase64(plaintext, &key_raw);
386

28
  if (!retval || (key_raw.size() != cipher::CipherAes256Cbc::kKeySize)) {
387
    return kFailDecrypt;
388
  }
389
28
  key_onetime_ = cipher::Key::CreateFromString(key_raw);
390
28
  assert(key_onetime_);
391
28
  return kFailOk;
392
}
393
394
395
/**
396
 * Creates a macaroon based on the JSON object that the lease server produced.
397
 */
398
14
Macaroon *Macaroon::ParseOnPublisher(
399
  const string &json,
400
  cipher::AbstractKeyDatabase *key_db,
401
  VerifyFailures *failure_code)
402
{
403
14
  assert(key_db != NULL);
404
14
  assert(failure_code != NULL);
405
14
  *failure_code = kFailUnknown;
406
407
14
  UniquePtr<JsonDocument> json_document(JsonDocument::Create(json));
408
14
  if (!json_document.IsValid()) {
409
    *failure_code = kFailBadJson;
410
    return NULL;
411
  }
412
413
14
  UniquePtr<Macaroon> macaroon(new Macaroon());
414
14
  bool retval = macaroon->CreateFromJson(json_document->root()->first_child);
415
14
  if (!retval) {
416
    *failure_code = kFailBadMacaroon;
417
    return NULL;
418
  }
419
14
  if (macaroon->expiry_utc_ < time(NULL)) {
420
    *failure_code = kFailExpired;
421
    return NULL;
422
  }
423
424
  // Extract the random root key for 3rd party caveats
425
14
  macaroon->secret_key_publisher_ = key_db->Find(macaroon->key_id_publisher_);
426
14
  if (macaroon->secret_key_publisher_ == NULL) {
427
    *failure_code = kFailUnknownKey;
428
    return NULL;
429
  }
430
  *failure_code = macaroon->ExtractOnetimeKey(macaroon->key_onetime4publisher_,
431
14
                                              *macaroon->secret_key_publisher_);
432
14
  if (*failure_code != kFailOk)
433
    return NULL;
434
14
  return macaroon.Release();
435
}
436
437
438
/**
439
 * Creates a macaroon based on the JSON object that the lease server produced
440
 * and the release manager machine attenuated.
441
 */
442
14
Macaroon *Macaroon::ParseOnStorage(
443
  const string &json,
444
  cipher::AbstractKeyDatabase *key_db,
445
  VerifyFailures *failure_code)
446
{
447
14
  assert(key_db != NULL);
448
14
  assert(failure_code != NULL);
449
14
  *failure_code = kFailUnknown;
450
451
14
  UniquePtr<JsonDocument> json_document(JsonDocument::Create(json));
452
14
  if (!json_document.IsValid()) {
453
    *failure_code = kFailBadJson;
454
    return NULL;
455
  }
456
457
14
  UniquePtr<Macaroon> macaroon(new Macaroon());
458
  bool retval =
459
14
    macaroon->CreateFromJson(json_document->root()->first_child);
460
14
  if (!retval) {
461
    *failure_code = kFailBadMacaroon;
462
    return NULL;
463
  }
464
14
  time_t now = time(NULL);
465

14
  if ((macaroon->expiry_utc_ < now) || (macaroon->expiry_utc_operation_ < now))
466
  {
467
    *failure_code = kFailExpired;
468
    return NULL;
469
  }
470
471
  // Extract the random root key for 3rd party caveats
472
14
  macaroon->secret_key_storage_ = key_db->Find(macaroon->key_id_storage_);
473
14
  if (macaroon->secret_key_storage_ == NULL) {
474
    *failure_code = kFailUnknownKey;
475
    return NULL;
476
  }
477
  *failure_code = macaroon->ExtractOnetimeKey(macaroon->key_onetime4storage_,
478
14
                                              *macaroon->secret_key_storage_);
479
14
  if (*failure_code != kFailOk)
480
    return NULL;
481
482
14
  shash::Any retrieved_primary_hmac(macaroon->hmac_primary_);
483
14
  macaroon->ComputePrimaryHmac();
484
14
  if (retrieved_primary_hmac != macaroon->hmac_primary_) {
485
    *failure_code = kFailBadPrimarySignature;
486
    return NULL;
487
  }
488
489
14
  shash::Any retrieved_3rdparty_hmac(macaroon->hmac_3rd_party_);
490
14
  macaroon->Compute3rdPartyHmac();
491
14
  if (retrieved_3rdparty_hmac != macaroon->hmac_3rd_party_) {
492
    *failure_code = kFailBad3rdPartySignature;
493
    return NULL;
494
  }
495
496
14
  return macaroon.Release();
497
}