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