GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/signing_tool.cc
Date: 2026-09-06 02:40:30
Exec Total Coverage
Lines: 0 159 0.0%
Branches: 0 284 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 <memory>
8 #include <string>
9
10 #include "manifest.h"
11 #include "object_fetcher.h"
12 #include "reflog.h"
13 #include "server_tool.h"
14 #include "upload.h"
15 #include "util/exception.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 std::unique_ptr<upload::Spooler> spooler;
46 std::unique_ptr<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 = std::unique_ptr<manifest::Manifest>(
68 manifest::Manifest::LoadFile(manifest_path));
69 if (manifest.get() == nullptr) {
70 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to parse manifest");
71 return kError;
72 }
73
74 // reflog_chksum_path wasn't given, the reflog checksum can possibly be
75 // obtained from the manifest
76 if (reflog_chksum_path.empty()) {
77 reflog_hash = manifest->reflog_hash();
78 }
79
80 // Connect to the spooler
81 const upload::SpoolerDefinition sd(spooler_definition,
82 manifest->GetHashAlgorithm());
83 spooler = std::unique_ptr<upload::Spooler>(upload::Spooler::Construct(sd));
84 if (spooler.get() == nullptr) {
85 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to setup upload spooler");
86 return kInitError;
87 }
88
89 std::unique_ptr<manifest::Reflog> reflog;
90 if (!reflog_hash.IsNull()) {
91 reflog.reset(
92 server_tool_->FetchReflog(&object_fetcher, repo_name, reflog_hash));
93 if (reflog.get() == nullptr) {
94 LogCvmfs(kLogCvmfs, kLogStderr, "reflog missing");
95 return kReflogMissing;
96 }
97 } else {
98 LogCvmfs(kLogCvmfs, kLogVerboseMsg, "no reflog (ignoring)");
99 if (spooler->Peek(".cvmfsreflog")) {
100 LogCvmfs(kLogCvmfs, kLogStderr,
101 "no reflog hash specified but reflog is present");
102 return kError;
103 }
104 }
105
106 // From here on things are potentially put in backend storage
107
108 // Register callback for retrieving the certificate hash
109 upload::Spooler::CallbackPtr callback = spooler->RegisterListener(
110 &SigningTool::CertificateUploadCallback, this);
111
112 // Safe certificate (and wait for the upload through a Future)
113 spooler->ProcessCertificate(certificate);
114 const shash::Any certificate_hash = certificate_hash_.Get();
115 spooler->UnregisterListener(callback);
116
117 if (certificate_hash.IsNull()) {
118 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to upload certificate");
119 return kError;
120 }
121
122 // Safe repository meta info file
123 shash::Any metainfo_hash = manifest->meta_info();
124 if (!meta_info.empty()) {
125 upload::Spooler::CallbackPtr callback = spooler->RegisterListener(
126 &SigningTool::MetainfoUploadCallback, this);
127 spooler->ProcessMetainfo(meta_info);
128 metainfo_hash = metainfo_hash_.Get();
129 spooler->UnregisterListener(callback);
130
131 if (metainfo_hash.IsNull()) {
132 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to upload meta info");
133 return kError;
134 }
135 }
136
137 // Update Reflog database
138 if (reflog.get() != nullptr) {
139 reflog->BeginTransaction();
140
141 if (!reflog->AddCatalog(manifest->catalog_hash())) {
142 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to add catalog to Reflog");
143 return kError;
144 }
145
146 if (!reflog->AddCertificate(certificate_hash)) {
147 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to add certificate to Reflog");
148 return kError;
149 }
150
151 if (!manifest->history().IsNull()) {
152 if (!reflog->AddHistory(manifest->history())) {
153 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to add history to Reflog");
154 return kError;
155 }
156 }
157
158 if (!metainfo_hash.IsNull()) {
159 if (!reflog->AddMetainfo(metainfo_hash)) {
160 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to add meta info to Reflog");
161 return kError;
162 }
163 }
164
165 // Callers of SigningTool may provide a list of additional catalogs that
166 // need to be added to reflog (e. g. for later garbage collection)
167 std::vector<shash::Any>::const_iterator i = reflog_catalogs.begin();
168 const std::vector<shash::Any>::const_iterator iend = reflog_catalogs.end();
169 for (; i != iend; ++i) {
170 if (!reflog->AddCatalog(*i)) {
171 LogCvmfs(kLogCvmfs, kLogStderr,
172 "Failed to add additional catalog %s to Reflog",
173 (*i).ToString().c_str());
174 return kError;
175 }
176 }
177
178 reflog->CommitTransaction();
179
180 // upload Reflog database
181 reflog->DropDatabaseFileOwnership();
182 const std::string reflog_db_file = reflog->database_file();
183 reflog.reset();
184 spooler->UploadReflog(reflog_db_file);
185 spooler->WaitForUpload();
186 reflog_hash.algorithm = manifest->GetHashAlgorithm();
187 manifest::Reflog::HashDatabase(reflog_db_file, &reflog_hash);
188 unlink(reflog_db_file.c_str());
189 if (spooler->GetNumberOfErrors()) {
190 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to upload Reflog (errors: %d)",
191 spooler->GetNumberOfErrors());
192 return kError;
193 }
194 if (!reflog_chksum_path.empty())
195 manifest::Reflog::WriteChecksum(reflog_chksum_path, reflog_hash);
196 }
197
198 // Don't activate new manifest, just make sure all its references are uploaded
199 // and entered into the reflog
200 if (return_early) {
201 return kSuccess;
202 }
203
204 // Update manifest
205 manifest->set_certificate(certificate_hash);
206 manifest->set_repository_name(repo_name);
207 manifest->set_publish_timestamp(time(NULL));
208 manifest->set_garbage_collectability(garbage_collectable);
209 manifest->set_has_alt_catalog_path(bootstrap_shortcuts);
210 if (!metainfo_hash.IsNull()) {
211 manifest->set_meta_info(metainfo_hash);
212 }
213 if (!reflog_hash.IsNull()) {
214 manifest->set_reflog_hash(reflog_hash);
215 }
216
217 std::string signed_manifest = manifest->ExportString();
218 shash::Any published_hash(manifest->GetHashAlgorithm());
219 shash::HashMem(
220 reinterpret_cast<const unsigned char *>(signed_manifest.data()),
221 signed_manifest.length(), &published_hash);
222 signed_manifest += "--\n" + published_hash.ToString() + "\n";
223
224 // Create alternative bootstrapping symlinks for VOMS secured repos
225 if (manifest->has_alt_catalog_path()) {
226 const bool success = spooler->PlaceBootstrappingShortcut(
227 manifest->certificate())
228 && spooler->PlaceBootstrappingShortcut(
229 manifest->catalog_hash())
230 && (manifest->history().IsNull()
231 || spooler->PlaceBootstrappingShortcut(
232 manifest->history()))
233 && (metainfo_hash.IsNull()
234 || spooler->PlaceBootstrappingShortcut(
235 metainfo_hash));
236
237 if (!success) {
238 LogCvmfs(kLogCvmfs, kLogStderr,
239 "failed to place VOMS bootstrapping "
240 "symlinks");
241 return kError;
242 }
243 }
244
245 // Sign manifest
246 unsigned char *sig;
247 unsigned sig_size;
248 const bool manifest_was_signed = server_tool_->signature_manager()->Sign(
249 reinterpret_cast<const unsigned char *>(published_hash.ToString().data()),
250 published_hash.GetHexSize(), &sig, &sig_size);
251 if (!manifest_was_signed) {
252 PANIC(kLogStderr, "Failed to sign manifest");
253 }
254
255 // Write new manifest
256 signed_manifest += std::string(reinterpret_cast<char *>(sig), sig_size);
257 free(sig);
258 if (!SafeWriteToFile(signed_manifest, manifest_path, 0664)) {
259 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to write manifest (errno: %d)",
260 errno);
261 return kError;
262 }
263
264 // Upload manifest
265 spooler->UploadManifest(manifest_path);
266 spooler->WaitForUpload();
267 unlink(manifest_path.c_str());
268 if (spooler->GetNumberOfErrors()) {
269 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to commit manifest (errors: %d)",
270 spooler->GetNumberOfErrors());
271 return kError;
272 }
273
274 return kSuccess;
275 }
276
277 void SigningTool::CertificateUploadCallback(
278 const upload::SpoolerResult &result) {
279 shash::Any certificate_hash;
280 if (result.return_code == 0) {
281 certificate_hash = result.content_hash;
282 } else {
283 LogCvmfs(kLogCvmfs, kLogStderr,
284 "Failed to upload certificate "
285 "(retcode: %d)",
286 result.return_code);
287 }
288 certificate_hash_.Set(certificate_hash);
289 }
290
291 void SigningTool::MetainfoUploadCallback(const upload::SpoolerResult &result) {
292 shash::Any metainfo_hash;
293 if (result.return_code == 0) {
294 metainfo_hash = result.content_hash;
295 } else {
296 LogCvmfs(kLogCvmfs, kLogStderr, "Failed to upload meta info (retcode: %d)",
297 result.return_code);
298 }
299 metainfo_hash_.Set(metainfo_hash);
300 }
301