| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/manifest_fetch.cc |
| Date: | 2025-11-09 02:35:23 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 84 | 109 | 77.1% |
| Branches: | 64 | 117 | 54.7% |
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * This file is part of the CernVM File System. | ||
| 3 | */ | ||
| 4 | |||
| 5 | #include "manifest_fetch.h" | ||
| 6 | |||
| 7 | #include <cassert> | ||
| 8 | #include <cstring> | ||
| 9 | #include <string> | ||
| 10 | #include <vector> | ||
| 11 | |||
| 12 | #include "crypto/hash.h" | ||
| 13 | #include "crypto/signature.h" | ||
| 14 | #include "manifest.h" | ||
| 15 | #include "network/download.h" | ||
| 16 | #include "util/smalloc.h" | ||
| 17 | #include "whitelist.h" | ||
| 18 | |||
| 19 | using namespace std; // NOLINT | ||
| 20 | |||
| 21 | namespace manifest { | ||
| 22 | |||
| 23 | /** | ||
| 24 | * Verifies the manifest, the certificate, and the whitelist. | ||
| 25 | * If base_url is empty, uses the probe_hosts feature from download manager. | ||
| 26 | * | ||
| 27 | * @note Ownership of manifest_data is transferred to the ensemble. | ||
| 28 | */ | ||
| 29 | 647 | static Failures DoVerify(unsigned char *manifest_data, size_t manifest_size, | |
| 30 | const std::string &base_url, | ||
| 31 | const std::string &repository_name, | ||
| 32 | const uint64_t minimum_timestamp, | ||
| 33 | const shash::Any *base_catalog, | ||
| 34 | signature::SignatureManager *signature_manager, | ||
| 35 | download::DownloadManager *download_manager, | ||
| 36 | ManifestEnsemble *ensemble) { | ||
| 37 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 647 times.
|
647 | assert(ensemble); |
| 38 | 647 | const bool probe_hosts = base_url == ""; | |
| 39 | 647 | Failures result = kFailUnknown; | |
| 40 | bool retval_b; | ||
| 41 | download::Failures retval_dl; | ||
| 42 | whitelist::Failures retval_wl; | ||
| 43 | whitelist::Whitelist whitelist(repository_name, download_manager, | ||
| 44 |
1/2✓ Branch 1 taken 647 times.
✗ Branch 2 not taken.
|
647 | signature_manager); |
| 45 |
1/2✓ Branch 1 taken 647 times.
✗ Branch 2 not taken.
|
647 | string certificate_url = base_url + "/"; // rest is in manifest |
| 46 |
1/2✓ Branch 1 taken 647 times.
✗ Branch 2 not taken.
|
647 | shash::Any certificate_hash; |
| 47 | 647 | cvmfs::MemSink certificate_memsink; | |
| 48 | download::JobInfo download_certificate(&certificate_url, true, probe_hosts, | ||
| 49 | &certificate_hash, | ||
| 50 |
1/2✓ Branch 1 taken 647 times.
✗ Branch 2 not taken.
|
647 | &certificate_memsink); |
| 51 | |||
| 52 | // Load Manifest | ||
| 53 | 647 | ensemble->raw_manifest_buf = manifest_data; | |
| 54 | 647 | ensemble->raw_manifest_size = manifest_size; | |
| 55 |
1/2✓ Branch 1 taken 647 times.
✗ Branch 2 not taken.
|
647 | ensemble->manifest = manifest::Manifest::LoadMem(ensemble->raw_manifest_buf, |
| 56 | ensemble->raw_manifest_size); | ||
| 57 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 647 times.
|
647 | if (!ensemble->manifest) |
| 58 | ✗ | return kFailIncomplete; | |
| 59 | |||
| 60 | // Basic manifest sanity check | ||
| 61 |
3/6✓ Branch 1 taken 647 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 17 times.
✓ Branch 6 taken 630 times.
|
647 | if (ensemble->manifest->repository_name() != repository_name) { |
| 62 |
1/5✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
34 | LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr, |
| 63 | "repository name does not match (found %s, expected %s)", | ||
| 64 |
1/2✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
|
34 | ensemble->manifest->repository_name().c_str(), |
| 65 | repository_name.c_str()); | ||
| 66 | 17 | result = kFailNameMismatch; | |
| 67 | 17 | goto cleanup; | |
| 68 | } | ||
| 69 |
4/10✓ Branch 2 taken 630 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 630 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 630 times.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 630 times.
|
630 | if (ensemble->manifest->root_path() != shash::Md5(shash::AsciiPtr(""))) { |
| 70 | ✗ | result = kFailRootMismatch; | |
| 71 | ✗ | goto cleanup; | |
| 72 | } | ||
| 73 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 630 times.
|
630 | if (ensemble->manifest->publish_timestamp() < minimum_timestamp) { |
| 74 | ✗ | result = kFailOutdated; | |
| 75 | ✗ | goto cleanup; | |
| 76 | } | ||
| 77 | |||
| 78 | // Quick way out: hash matches base catalog | ||
| 79 |
6/6✓ Branch 0 taken 240 times.
✓ Branch 1 taken 390 times.
✓ Branch 4 taken 48 times.
✓ Branch 5 taken 192 times.
✓ Branch 6 taken 48 times.
✓ Branch 7 taken 582 times.
|
630 | if (base_catalog && (ensemble->manifest->catalog_hash() == *base_catalog)) { |
| 80 | 48 | return kFailOk; | |
| 81 | } | ||
| 82 | |||
| 83 | // Load certificate | ||
| 84 | 582 | certificate_hash = ensemble->manifest->certificate(); | |
| 85 |
1/2✓ Branch 1 taken 582 times.
✗ Branch 2 not taken.
|
582 | ensemble->FetchCertificate(certificate_hash); |
| 86 |
2/2✓ Branch 0 taken 580 times.
✓ Branch 1 taken 2 times.
|
582 | if (!ensemble->cert_buf) { |
| 87 |
2/4✓ Branch 1 taken 580 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 580 times.
✗ Branch 5 not taken.
|
580 | certificate_url += ensemble->manifest->MakeCertificatePath(); |
| 88 |
1/2✓ Branch 1 taken 580 times.
✗ Branch 2 not taken.
|
580 | retval_dl = download_manager->Fetch(&download_certificate); |
| 89 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 580 times.
|
580 | if (retval_dl != download::kFailOk) { |
| 90 | ✗ | result = kFailLoad; | |
| 91 | ✗ | goto cleanup; | |
| 92 | } | ||
| 93 | 580 | ensemble->cert_buf = certificate_memsink.data(); | |
| 94 | 580 | ensemble->cert_size = certificate_memsink.pos(); | |
| 95 | 580 | certificate_memsink.Release(); | |
| 96 | } | ||
| 97 |
1/2✓ Branch 1 taken 582 times.
✗ Branch 2 not taken.
|
582 | retval_b = signature_manager->LoadCertificateMem(ensemble->cert_buf, |
| 98 | ensemble->cert_size); | ||
| 99 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 582 times.
|
582 | if (!retval_b) { |
| 100 | ✗ | result = kFailBadCertificate; | |
| 101 | ✗ | goto cleanup; | |
| 102 | } | ||
| 103 | |||
| 104 | // Verify manifest | ||
| 105 | 1164 | retval_b = signature_manager->VerifyLetter( | |
| 106 |
1/2✓ Branch 1 taken 582 times.
✗ Branch 2 not taken.
|
582 | ensemble->raw_manifest_buf, ensemble->raw_manifest_size, false); |
| 107 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 582 times.
|
582 | if (!retval_b) { |
| 108 | ✗ | LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr, | |
| 109 | "failed to verify repository manifest"); | ||
| 110 | ✗ | result = kFailBadSignature; | |
| 111 | ✗ | goto cleanup; | |
| 112 | } | ||
| 113 | |||
| 114 | // Load whitelist and verify | ||
| 115 |
1/2✓ Branch 1 taken 582 times.
✗ Branch 2 not taken.
|
582 | retval_wl = whitelist.LoadUrl(base_url); |
| 116 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 582 times.
|
582 | if (retval_wl != whitelist::kFailOk) { |
| 117 | ✗ | LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr, | |
| 118 | "whitelist verification failed (%d): %s", retval_wl, | ||
| 119 | whitelist::Code2Ascii(retval_wl)); | ||
| 120 | ✗ | result = kFailBadWhitelist; | |
| 121 | ✗ | goto cleanup; | |
| 122 | } | ||
| 123 | |||
| 124 |
1/2✓ Branch 1 taken 582 times.
✗ Branch 2 not taken.
|
582 | retval_wl = whitelist.VerifyLoadedCertificate(); |
| 125 |
2/2✓ Branch 0 taken 34 times.
✓ Branch 1 taken 548 times.
|
582 | if (retval_wl != whitelist::kFailOk) { |
| 126 |
1/2✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
|
34 | LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr, |
| 127 | "failed to verify repository signature against whitelist (%d): %s", | ||
| 128 | retval_wl, whitelist::Code2Ascii(retval_wl)); | ||
| 129 | 34 | result = kFailInvalidCertificate; | |
| 130 | 34 | goto cleanup; | |
| 131 | } | ||
| 132 | |||
| 133 |
1/2✓ Branch 1 taken 548 times.
✗ Branch 2 not taken.
|
548 | whitelist.CopyBuffers(&ensemble->whitelist_size, &ensemble->whitelist_buf, |
| 134 | &ensemble->whitelist_pkcs7_size, | ||
| 135 | &ensemble->whitelist_pkcs7_buf); | ||
| 136 | |||
| 137 | 548 | return kFailOk; | |
| 138 | |||
| 139 | 51 | cleanup: | |
| 140 |
1/2✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
|
51 | delete ensemble->manifest; |
| 141 | 51 | ensemble->manifest = NULL; | |
| 142 |
1/2✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
|
51 | if (ensemble->raw_manifest_buf) |
| 143 | 51 | free(ensemble->raw_manifest_buf); | |
| 144 |
2/2✓ Branch 0 taken 34 times.
✓ Branch 1 taken 17 times.
|
51 | if (ensemble->cert_buf) |
| 145 | 34 | free(ensemble->cert_buf); | |
| 146 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (ensemble->whitelist_buf) |
| 147 | ✗ | free(ensemble->whitelist_buf); | |
| 148 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (ensemble->whitelist_pkcs7_buf) |
| 149 | ✗ | free(ensemble->whitelist_pkcs7_buf); | |
| 150 | 51 | ensemble->raw_manifest_buf = NULL; | |
| 151 | 51 | ensemble->cert_buf = NULL; | |
| 152 | 51 | ensemble->whitelist_buf = NULL; | |
| 153 | 51 | ensemble->whitelist_pkcs7_buf = NULL; | |
| 154 | 51 | ensemble->raw_manifest_size = 0; | |
| 155 | 51 | ensemble->cert_size = 0; | |
| 156 | 51 | ensemble->whitelist_size = 0; | |
| 157 | 51 | ensemble->whitelist_pkcs7_size = 0; | |
| 158 | 51 | return result; | |
| 159 | 647 | } | |
| 160 | |||
| 161 | /** | ||
| 162 | * Downloads and verifies the manifest, the certificate, and the whitelist. | ||
| 163 | * If base_url is empty, uses the probe_hosts feature from download manager. | ||
| 164 | */ | ||
| 165 | 712 | static Failures DoFetch(const std::string &base_url, | |
| 166 | const std::string &repository_name, | ||
| 167 | const uint64_t minimum_timestamp, | ||
| 168 | const shash::Any *base_catalog, | ||
| 169 | signature::SignatureManager *signature_manager, | ||
| 170 | download::DownloadManager *download_manager, | ||
| 171 | ManifestEnsemble *ensemble) { | ||
| 172 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 712 times.
|
712 | assert(ensemble); |
| 173 | 712 | const bool probe_hosts = base_url == ""; | |
| 174 | download::Failures retval_dl; | ||
| 175 |
2/4✓ Branch 2 taken 712 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 712 times.
✗ Branch 6 not taken.
|
1424 | const string manifest_url = base_url + string("/.cvmfspublished"); |
| 176 | 712 | cvmfs::MemSink manifest_memsink; | |
| 177 | download::JobInfo download_manifest(&manifest_url, false, probe_hosts, NULL, | ||
| 178 |
1/2✓ Branch 1 taken 712 times.
✗ Branch 2 not taken.
|
712 | &manifest_memsink); |
| 179 | |||
| 180 |
1/2✓ Branch 1 taken 712 times.
✗ Branch 2 not taken.
|
712 | retval_dl = download_manager->Fetch(&download_manifest); |
| 181 |
2/2✓ Branch 0 taken 65 times.
✓ Branch 1 taken 647 times.
|
712 | if (retval_dl != download::kFailOk) { |
| 182 |
1/2✓ Branch 2 taken 65 times.
✗ Branch 3 not taken.
|
65 | LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn, |
| 183 | "failed to download repository manifest (%d - %s)", retval_dl, | ||
| 184 | download::Code2Ascii(retval_dl)); | ||
| 185 | 65 | return kFailLoad; | |
| 186 | } | ||
| 187 | |||
| 188 | 647 | manifest_memsink.Release(); | |
| 189 |
1/2✓ Branch 3 taken 647 times.
✗ Branch 4 not taken.
|
647 | return DoVerify(manifest_memsink.data(), manifest_memsink.pos(), base_url, |
| 190 | repository_name, minimum_timestamp, base_catalog, | ||
| 191 | 647 | signature_manager, download_manager, ensemble); | |
| 192 | 712 | } | |
| 193 | |||
| 194 | /** | ||
| 195 | * If the whitelist or the manifest are corrupted, fail-over once to another | ||
| 196 | * stratum 1 if more than a single stratum 1 is known. | ||
| 197 | */ | ||
| 198 | 712 | Failures Fetch(const std::string &base_url, const std::string &repository_name, | |
| 199 | const uint64_t minimum_timestamp, const shash::Any *base_catalog, | ||
| 200 | signature::SignatureManager *signature_manager, | ||
| 201 | download::DownloadManager *download_manager, | ||
| 202 | ManifestEnsemble *ensemble) { | ||
| 203 | 712 | Failures result = DoFetch(base_url, repository_name, minimum_timestamp, | |
| 204 | base_catalog, signature_manager, download_manager, | ||
| 205 | ensemble); | ||
| 206 |
2/2✓ Branch 0 taken 51 times.
✓ Branch 1 taken 65 times.
|
116 | if ((result != kFailOk) && (result != kFailLoad) |
| 207 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 34 times.
|
51 | && (result != kFailInvalidCertificate) |
| 208 |
4/6✓ Branch 0 taken 116 times.
✓ Branch 1 taken 596 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 712 times.
|
828 | && (download_manager->num_hosts() > 1)) { |
| 209 | ✗ | LogCvmfs(kLogCache, kLogDebug | kLogSyslogWarn, | |
| 210 | "failed to fetch manifest (%d - %s), trying another stratum 1", | ||
| 211 | result, Code2Ascii(result)); | ||
| 212 | ✗ | download_manager->SwitchHost(); | |
| 213 | ✗ | result = DoFetch(base_url, repository_name, minimum_timestamp, base_catalog, | |
| 214 | signature_manager, download_manager, ensemble); | ||
| 215 | } | ||
| 216 | 712 | return result; | |
| 217 | } | ||
| 218 | |||
| 219 | /** | ||
| 220 | * Verifies the manifest, the certificate, and the whitelist. | ||
| 221 | * If base_url is empty, uses the probe_hosts feature from download manager. | ||
| 222 | * Creates a copy of the manifest to verify. | ||
| 223 | */ | ||
| 224 | ✗ | Failures Verify(unsigned char *manifest_data, size_t manifest_size, | |
| 225 | const std::string &base_url, const std::string &repository_name, | ||
| 226 | const uint64_t minimum_timestamp, | ||
| 227 | const shash::Any *base_catalog, | ||
| 228 | signature::SignatureManager *signature_manager, | ||
| 229 | download::DownloadManager *download_manager, | ||
| 230 | ManifestEnsemble *ensemble) { | ||
| 231 | unsigned char *manifest_copy = reinterpret_cast<unsigned char *>( | ||
| 232 | ✗ | smalloc(manifest_size)); | |
| 233 | ✗ | memcpy(manifest_copy, manifest_data, manifest_size); | |
| 234 | ✗ | return DoVerify(manifest_copy, manifest_size, base_url, repository_name, | |
| 235 | minimum_timestamp, base_catalog, signature_manager, | ||
| 236 | ✗ | download_manager, ensemble); | |
| 237 | } | ||
| 238 | |||
| 239 | } // namespace manifest | ||
| 240 |