Directory: | cvmfs/ |
---|---|
File: | cvmfs/manifest_fetch.cc |
Date: | 2025-06-22 02:36:02 |
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 | 1128 | 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 1128 times.
|
1128 | assert(ensemble); |
38 | 1128 | const bool probe_hosts = base_url == ""; | |
39 | 1128 | 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 1128 times.
✗ Branch 2 not taken.
|
1128 | signature_manager); |
45 |
1/2✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
|
1128 | string certificate_url = base_url + "/"; // rest is in manifest |
46 |
1/2✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
|
1128 | shash::Any certificate_hash; |
47 | 1128 | cvmfs::MemSink certificate_memsink; | |
48 | download::JobInfo download_certificate(&certificate_url, true, probe_hosts, | ||
49 | &certificate_hash, | ||
50 |
1/2✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
|
1128 | &certificate_memsink); |
51 | |||
52 | // Load Manifest | ||
53 | 1128 | ensemble->raw_manifest_buf = manifest_data; | |
54 | 1128 | ensemble->raw_manifest_size = manifest_size; | |
55 |
1/2✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
|
1128 | ensemble->manifest = manifest::Manifest::LoadMem(ensemble->raw_manifest_buf, |
56 | ensemble->raw_manifest_size); | ||
57 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1128 times.
|
1128 | if (!ensemble->manifest) |
58 | ✗ | return kFailIncomplete; | |
59 | |||
60 | // Basic manifest sanity check | ||
61 |
3/6✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 49 times.
✓ Branch 6 taken 1079 times.
|
1128 | if (ensemble->manifest->repository_name() != repository_name) { |
62 |
1/5✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
98 | LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr, |
63 | "repository name does not match (found %s, expected %s)", | ||
64 |
1/2✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
|
98 | ensemble->manifest->repository_name().c_str(), |
65 | repository_name.c_str()); | ||
66 | 49 | result = kFailNameMismatch; | |
67 | 49 | goto cleanup; | |
68 | } | ||
69 |
4/10✓ Branch 2 taken 1079 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 1079 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 1079 times.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 1079 times.
|
1079 | 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 1079 times.
|
1079 | 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 540 times.
✓ Branch 1 taken 539 times.
✓ Branch 4 taken 99 times.
✓ Branch 5 taken 441 times.
✓ Branch 6 taken 99 times.
✓ Branch 7 taken 980 times.
|
1079 | if (base_catalog && (ensemble->manifest->catalog_hash() == *base_catalog)) { |
80 | 99 | return kFailOk; | |
81 | } | ||
82 | |||
83 | // Load certificate | ||
84 | 980 | certificate_hash = ensemble->manifest->certificate(); | |
85 |
1/2✓ Branch 1 taken 980 times.
✗ Branch 2 not taken.
|
980 | ensemble->FetchCertificate(certificate_hash); |
86 |
2/2✓ Branch 0 taken 978 times.
✓ Branch 1 taken 2 times.
|
980 | if (!ensemble->cert_buf) { |
87 |
2/4✓ Branch 1 taken 978 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 978 times.
✗ Branch 5 not taken.
|
978 | certificate_url += ensemble->manifest->MakeCertificatePath(); |
88 |
1/2✓ Branch 1 taken 978 times.
✗ Branch 2 not taken.
|
978 | retval_dl = download_manager->Fetch(&download_certificate); |
89 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 978 times.
|
978 | if (retval_dl != download::kFailOk) { |
90 | ✗ | result = kFailLoad; | |
91 | ✗ | goto cleanup; | |
92 | } | ||
93 | 978 | ensemble->cert_buf = certificate_memsink.data(); | |
94 | 978 | ensemble->cert_size = certificate_memsink.pos(); | |
95 | 978 | certificate_memsink.Release(); | |
96 | } | ||
97 |
1/2✓ Branch 1 taken 980 times.
✗ Branch 2 not taken.
|
980 | retval_b = signature_manager->LoadCertificateMem(ensemble->cert_buf, |
98 | ensemble->cert_size); | ||
99 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 980 times.
|
980 | if (!retval_b) { |
100 | ✗ | result = kFailBadCertificate; | |
101 | ✗ | goto cleanup; | |
102 | } | ||
103 | |||
104 | // Verify manifest | ||
105 | 1960 | retval_b = signature_manager->VerifyLetter( | |
106 |
1/2✓ Branch 1 taken 980 times.
✗ Branch 2 not taken.
|
980 | ensemble->raw_manifest_buf, ensemble->raw_manifest_size, false); |
107 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 980 times.
|
980 | 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 980 times.
✗ Branch 2 not taken.
|
980 | retval_wl = whitelist.LoadUrl(base_url); |
116 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 980 times.
|
980 | 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 980 times.
✗ Branch 2 not taken.
|
980 | retval_wl = whitelist.VerifyLoadedCertificate(); |
125 |
2/2✓ Branch 0 taken 98 times.
✓ Branch 1 taken 882 times.
|
980 | if (retval_wl != whitelist::kFailOk) { |
126 |
1/2✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
|
98 | LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr, |
127 | "failed to verify repository signature against whitelist (%d): %s", | ||
128 | retval_wl, whitelist::Code2Ascii(retval_wl)); | ||
129 | 98 | result = kFailInvalidCertificate; | |
130 | 98 | goto cleanup; | |
131 | } | ||
132 | |||
133 |
1/2✓ Branch 1 taken 882 times.
✗ Branch 2 not taken.
|
882 | whitelist.CopyBuffers(&ensemble->whitelist_size, &ensemble->whitelist_buf, |
134 | &ensemble->whitelist_pkcs7_size, | ||
135 | &ensemble->whitelist_pkcs7_buf); | ||
136 | |||
137 | 882 | return kFailOk; | |
138 | |||
139 | 147 | cleanup: | |
140 |
1/2✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
|
147 | delete ensemble->manifest; |
141 | 147 | ensemble->manifest = NULL; | |
142 |
1/2✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
|
147 | if (ensemble->raw_manifest_buf) |
143 | 147 | free(ensemble->raw_manifest_buf); | |
144 |
2/2✓ Branch 0 taken 98 times.
✓ Branch 1 taken 49 times.
|
147 | if (ensemble->cert_buf) |
145 | 98 | free(ensemble->cert_buf); | |
146 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 147 times.
|
147 | if (ensemble->whitelist_buf) |
147 | ✗ | free(ensemble->whitelist_buf); | |
148 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 147 times.
|
147 | if (ensemble->whitelist_pkcs7_buf) |
149 | ✗ | free(ensemble->whitelist_pkcs7_buf); | |
150 | 147 | ensemble->raw_manifest_buf = NULL; | |
151 | 147 | ensemble->cert_buf = NULL; | |
152 | 147 | ensemble->whitelist_buf = NULL; | |
153 | 147 | ensemble->whitelist_pkcs7_buf = NULL; | |
154 | 147 | ensemble->raw_manifest_size = 0; | |
155 | 147 | ensemble->cert_size = 0; | |
156 | 147 | ensemble->whitelist_size = 0; | |
157 | 147 | ensemble->whitelist_pkcs7_size = 0; | |
158 | 147 | return result; | |
159 | 1128 | } | |
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 | 1272 | 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 1272 times.
|
1272 | assert(ensemble); |
173 | 1272 | const bool probe_hosts = base_url == ""; | |
174 | download::Failures retval_dl; | ||
175 |
2/4✓ Branch 2 taken 1272 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1272 times.
✗ Branch 6 not taken.
|
2544 | const string manifest_url = base_url + string("/.cvmfspublished"); |
176 | 1272 | cvmfs::MemSink manifest_memsink; | |
177 | download::JobInfo download_manifest(&manifest_url, false, probe_hosts, NULL, | ||
178 |
1/2✓ Branch 1 taken 1272 times.
✗ Branch 2 not taken.
|
1272 | &manifest_memsink); |
179 | |||
180 |
1/2✓ Branch 1 taken 1272 times.
✗ Branch 2 not taken.
|
1272 | retval_dl = download_manager->Fetch(&download_manifest); |
181 |
2/2✓ Branch 0 taken 144 times.
✓ Branch 1 taken 1128 times.
|
1272 | if (retval_dl != download::kFailOk) { |
182 |
1/2✓ Branch 2 taken 144 times.
✗ Branch 3 not taken.
|
144 | LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn, |
183 | "failed to download repository manifest (%d - %s)", retval_dl, | ||
184 | download::Code2Ascii(retval_dl)); | ||
185 | 144 | return kFailLoad; | |
186 | } | ||
187 | |||
188 | 1128 | manifest_memsink.Release(); | |
189 |
1/2✓ Branch 3 taken 1128 times.
✗ Branch 4 not taken.
|
1128 | return DoVerify(manifest_memsink.data(), manifest_memsink.pos(), base_url, |
190 | repository_name, minimum_timestamp, base_catalog, | ||
191 | 1128 | signature_manager, download_manager, ensemble); | |
192 | 1272 | } | |
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 | 1272 | 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 | 1272 | Failures result = DoFetch(base_url, repository_name, minimum_timestamp, | |
204 | base_catalog, signature_manager, download_manager, | ||
205 | ensemble); | ||
206 |
2/2✓ Branch 0 taken 147 times.
✓ Branch 1 taken 144 times.
|
291 | if ((result != kFailOk) && (result != kFailLoad) |
207 |
2/2✓ Branch 0 taken 49 times.
✓ Branch 1 taken 98 times.
|
147 | && (result != kFailInvalidCertificate) |
208 |
4/6✓ Branch 0 taken 291 times.
✓ Branch 1 taken 981 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1272 times.
|
1563 | && (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 | 1272 | 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 |