GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/signing_tool.cc
Date: 2025-06-29 02:35:41
Exec Total Coverage
Lines: 0 157 0.0%
Branches: 0 298 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System
3 */
4
5 #include "signing_tool.h"
6
7 #include <string>
8
9 #include "manifest.h"
10 #include "object_fetcher.h"
11 #include "reflog.h"
12 #include "server_tool.h"
13 #include "upload.h"
14 #include "util/exception.h"
15 #include "util/pointer.h"
16
17 namespace {
18
19 typedef HttpObjectFetcher<> ObjectFetcher;
20
21 } // namespace
22
23 SigningTool::SigningTool(ServerTool *server_tool)
24 : server_tool_(server_tool) { }
25
26 SigningTool::~SigningTool() { }
27
28 SigningTool::Result SigningTool::Run(
29 const std::string &manifest_path, const std::string &repo_url,
30 const std::string &spooler_definition, const std::string &temp_dir,
31 const std::string &certificate, const std::string &priv_key,
32 const std::string &repo_name, const std::string &pwd,
33 const std::string &meta_info, const std::string &reflog_chksum_path,
34 const std::string &proxy, const bool garbage_collectable,
35 const bool bootstrap_shortcuts, const bool return_early,
36 const std::vector<shash::Any> reflog_catalogs) {
37 shash::Any reflog_hash;
38 if (reflog_chksum_path != "") {
39 if (!manifest::Reflog::ReadChecksum(reflog_chksum_path, &reflog_hash)) {
40 LogCvmfs(kLogCvmfs, kLogStderr, "Could not read reflog checksum");
41 return kReflogChecksumMissing;
42 }
43 }
44
45 UniquePtr<upload::Spooler> spooler;
46 UniquePtr<manifest::Manifest> manifest;
47
48 if (!DirectoryExists(temp_dir)) {
49 LogCvmfs(kLogCvmfs, kLogStderr, "%s does not exist", temp_dir.c_str());
50 return kError;
51 }
52
53 // prepare global manager modules
54 const bool follow_redirects = false;
55 if (!server_tool_->InitDownloadManager(follow_redirects, proxy)
56 || !server_tool_->InitSignatureManager("", certificate, priv_key)) {
57 LogCvmfs(kLogCvmfs, kLogStderr, "failed to init repo connection");
58 return kInitError;
59 }
60
61 // init the download helper
62 ObjectFetcher object_fetcher(repo_name, repo_url, temp_dir,
63 server_tool_->download_manager(),
64 server_tool_->signature_manager());
65
66 // Load Manifest
67 manifest = manifest::Manifest::LoadFile(manifest_path);
68 if (!manifest.IsValid()) {
69 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to parse manifest");
70 return kError;
71 }
72
73 // reflog_chksum_path wasn't given, the reflog checksum can possibly be
74 // obtained from the manifest
75 if (reflog_chksum_path.empty()) {
76 reflog_hash = manifest->reflog_hash();
77 }
78
79 // Connect to the spooler
80 const upload::SpoolerDefinition sd(spooler_definition,
81 manifest->GetHashAlgorithm());
82 spooler = upload::Spooler::Construct(sd);
83 if (!spooler.IsValid()) {
84 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to setup upload spooler");
85 return kInitError;
86 }
87
88 UniquePtr<manifest::Reflog> reflog;
89 if (!reflog_hash.IsNull()) {
90 reflog = server_tool_->FetchReflog(&object_fetcher, repo_name, reflog_hash);
91 if (!reflog.IsValid()) {
92 LogCvmfs(kLogCvmfs, kLogStderr, "reflog missing");
93 return kReflogMissing;
94 }
95 } else {
96 LogCvmfs(kLogCvmfs, kLogVerboseMsg, "no reflog (ignoring)");
97 if (spooler->Peek(".cvmfsreflog")) {
98 LogCvmfs(kLogCvmfs, kLogStderr,
99 "no reflog hash specified but reflog is present");
100 return kError;
101 }
102 }
103
104 // From here on things are potentially put in backend storage
105
106 // Register callback for retrieving the certificate hash
107 upload::Spooler::CallbackPtr callback = spooler->RegisterListener(
108 &SigningTool::CertificateUploadCallback, this);
109
110 // Safe certificate (and wait for the upload through a Future)
111 spooler->ProcessCertificate(certificate);
112 const shash::Any certificate_hash = certificate_hash_.Get();
113 spooler->UnregisterListener(callback);
114
115 if (certificate_hash.IsNull()) {
116 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to upload certificate");
117 return kError;
118 }
119
120 // Safe repository meta info file
121 shash::Any metainfo_hash = manifest->meta_info();
122 if (!meta_info.empty()) {
123 upload::Spooler::CallbackPtr callback = spooler->RegisterListener(
124 &SigningTool::MetainfoUploadCallback, this);
125 spooler->ProcessMetainfo(meta_info);
126 metainfo_hash = metainfo_hash_.Get();
127 spooler->UnregisterListener(callback);
128
129 if (metainfo_hash.IsNull()) {
130 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to upload meta info");
131 return kError;
132 }
133 }
134
135 // Update Reflog database
136 if (reflog.IsValid()) {
137 reflog->BeginTransaction();
138
139 if (!reflog->AddCatalog(manifest->catalog_hash())) {
140 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to add catalog to Reflog");
141 return kError;
142 }
143
144 if (!reflog->AddCertificate(certificate_hash)) {
145 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to add certificate to Reflog");
146 return kError;
147 }
148
149 if (!manifest->history().IsNull()) {
150 if (!reflog->AddHistory(manifest->history())) {
151 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to add history to Reflog");
152 return kError;
153 }
154 }
155
156 if (!metainfo_hash.IsNull()) {
157 if (!reflog->AddMetainfo(metainfo_hash)) {
158 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to add meta info to Reflog");
159 return kError;
160 }
161 }
162
163 // Callers of SigningTool may provide a list of additional catalogs that
164 // need to be added to reflog (e. g. for later garbage collection)
165 std::vector<shash::Any>::const_iterator i = reflog_catalogs.begin();
166 const std::vector<shash::Any>::const_iterator iend = reflog_catalogs.end();
167 for (; i != iend; ++i) {
168 if (!reflog->AddCatalog(*i)) {
169 LogCvmfs(kLogCvmfs, kLogStderr,
170 "Failed to add additional catalog %s to Reflog",
171 (*i).ToString().c_str());
172 return kError;
173 }
174 }
175
176 reflog->CommitTransaction();
177
178 // upload Reflog database
179 reflog->DropDatabaseFileOwnership();
180 const std::string reflog_db_file = reflog->database_file();
181 reflog.Destroy();
182 spooler->UploadReflog(reflog_db_file);
183 spooler->WaitForUpload();
184 reflog_hash.algorithm = manifest->GetHashAlgorithm();
185 manifest::Reflog::HashDatabase(reflog_db_file, &reflog_hash);
186 unlink(reflog_db_file.c_str());
187 if (spooler->GetNumberOfErrors()) {
188 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to upload Reflog (errors: %d)",
189 spooler->GetNumberOfErrors());
190 return kError;
191 }
192 if (!reflog_chksum_path.empty())
193 manifest::Reflog::WriteChecksum(reflog_chksum_path, reflog_hash);
194 }
195
196 // Don't activate new manifest, just make sure all its references are uploaded
197 // and entered into the reflog
198 if (return_early) {
199 return kSuccess;
200 }
201
202 // Update manifest
203 manifest->set_certificate(certificate_hash);
204 manifest->set_repository_name(repo_name);
205 manifest->set_publish_timestamp(time(NULL));
206 manifest->set_garbage_collectability(garbage_collectable);
207 manifest->set_has_alt_catalog_path(bootstrap_shortcuts);
208 if (!metainfo_hash.IsNull()) {
209 manifest->set_meta_info(metainfo_hash);
210 }
211 if (!reflog_hash.IsNull()) {
212 manifest->set_reflog_hash(reflog_hash);
213 }
214
215 std::string signed_manifest = manifest->ExportString();
216 shash::Any published_hash(manifest->GetHashAlgorithm());
217 shash::HashMem(
218 reinterpret_cast<const unsigned char *>(signed_manifest.data()),
219 signed_manifest.length(), &published_hash);
220 signed_manifest += "--\n" + published_hash.ToString() + "\n";
221
222 // Create alternative bootstrapping symlinks for VOMS secured repos
223 if (manifest->has_alt_catalog_path()) {
224 const bool success = spooler->PlaceBootstrappingShortcut(
225 manifest->certificate())
226 && spooler->PlaceBootstrappingShortcut(
227 manifest->catalog_hash())
228 && (manifest->history().IsNull()
229 || spooler->PlaceBootstrappingShortcut(
230 manifest->history()))
231 && (metainfo_hash.IsNull()
232 || spooler->PlaceBootstrappingShortcut(
233 metainfo_hash));
234
235 if (!success) {
236 LogCvmfs(kLogCvmfs, kLogStderr,
237 "failed to place VOMS bootstrapping "
238 "symlinks");
239 return kError;
240 }
241 }
242
243 // Sign manifest
244 unsigned char *sig;
245 unsigned sig_size;
246 const bool manifest_was_signed = server_tool_->signature_manager()->Sign(
247 reinterpret_cast<const unsigned char *>(published_hash.ToString().data()),
248 published_hash.GetHexSize(), &sig, &sig_size);
249 if (!manifest_was_signed) {
250 PANIC(kLogStderr, "Failed to sign manifest");
251 }
252
253 // Write new manifest
254 signed_manifest += std::string(reinterpret_cast<char *>(sig), sig_size);
255 free(sig);
256 if (!SafeWriteToFile(signed_manifest, manifest_path, 0664)) {
257 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to write manifest (errno: %d)",
258 errno);
259 return kError;
260 }
261
262 // Upload manifest
263 spooler->UploadManifest(manifest_path);
264 spooler->WaitForUpload();
265 unlink(manifest_path.c_str());
266 if (spooler->GetNumberOfErrors()) {
267 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to commit manifest (errors: %d)",
268 spooler->GetNumberOfErrors());
269 return kError;
270 }
271
272 return kSuccess;
273 }
274
275 void SigningTool::CertificateUploadCallback(
276 const upload::SpoolerResult &result) {
277 shash::Any certificate_hash;
278 if (result.return_code == 0) {
279 certificate_hash = result.content_hash;
280 } else {
281 LogCvmfs(kLogCvmfs, kLogStderr,
282 "Failed to upload certificate "
283 "(retcode: %d)",
284 result.return_code);
285 }
286 certificate_hash_.Set(certificate_hash);
287 }
288
289 void SigningTool::MetainfoUploadCallback(const upload::SpoolerResult &result) {
290 shash::Any metainfo_hash;
291 if (result.return_code == 0) {
292 metainfo_hash = result.content_hash;
293 } else {
294 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to upload meta info (retcode: %d)",
295 result.return_code);
296 }
297 metainfo_hash_.Set(metainfo_hash);
298 }
299