1 |
|
|
/** |
2 |
|
|
* This file is part of the CernVM File System. |
3 |
|
|
*/ |
4 |
|
|
|
5 |
|
|
#include "cvmfs_config.h" |
6 |
|
|
#include "whitelist.h" |
7 |
|
|
|
8 |
|
|
#include <algorithm> |
9 |
|
|
#include <cassert> |
10 |
|
|
#include <cstring> |
11 |
|
|
#include <ctime> |
12 |
|
|
|
13 |
|
|
#include "download.h" |
14 |
|
|
#include "logging.h" |
15 |
|
|
#include "signature.h" |
16 |
|
|
#include "smalloc.h" |
17 |
|
|
#include "util/string.h" |
18 |
|
|
|
19 |
|
|
using namespace std; // NOLINT |
20 |
|
|
|
21 |
|
|
namespace whitelist { |
22 |
|
|
|
23 |
|
|
const int Whitelist::kFlagVerifyRsa = 0x01; |
24 |
|
|
const int Whitelist::kFlagVerifyPkcs7 = 0x02; |
25 |
|
|
const int Whitelist::kFlagVerifyCaChain = 0x04; |
26 |
|
|
|
27 |
|
|
|
28 |
|
26 |
void Whitelist::CopyBuffers(unsigned *plain_size, unsigned char **plain_buf, |
29 |
|
|
unsigned *pkcs7_size, unsigned char **pkcs7_buf) |
30 |
|
|
const |
31 |
|
|
{ |
32 |
|
26 |
*plain_size = plain_size_; |
33 |
|
26 |
*pkcs7_size = pkcs7_size_; |
34 |
|
26 |
*plain_buf = NULL; |
35 |
|
26 |
*pkcs7_buf = NULL; |
36 |
✓✗ |
26 |
if (plain_size_ > 0) { |
37 |
|
26 |
*plain_buf = reinterpret_cast<unsigned char *>(smalloc(plain_size_)); |
38 |
|
26 |
memcpy(*plain_buf, plain_buf_, plain_size_); |
39 |
|
|
} |
40 |
✗✓ |
26 |
if (pkcs7_size_ > 0) { |
41 |
|
|
*pkcs7_buf = reinterpret_cast<unsigned char *>(smalloc(pkcs7_size_)); |
42 |
|
|
memcpy(*pkcs7_buf, pkcs7_buf_, pkcs7_size_); |
43 |
|
|
} |
44 |
|
26 |
} |
45 |
|
|
|
46 |
|
|
|
47 |
|
|
time_t Whitelist::expires() { |
48 |
|
|
assert(status_ == kStAvailable); |
49 |
|
|
return expires_; |
50 |
|
|
} |
51 |
|
|
|
52 |
|
|
|
53 |
|
|
bool Whitelist::IsExpired() const { |
54 |
|
|
assert(status_ == kStAvailable); |
55 |
|
|
return time(NULL) > expires_; |
56 |
|
|
} |
57 |
|
|
|
58 |
|
|
|
59 |
|
32 |
Failures Whitelist::VerifyLoadedCertificate() const { |
60 |
✗✓ |
32 |
assert(status_ == kStAvailable); |
61 |
|
|
|
62 |
|
32 |
vector<string> blacklist = signature_manager_->GetBlacklist(); |
63 |
✓✓ |
35 |
for (unsigned i = 0; i < blacklist.size(); ++i) { |
64 |
|
|
shash::Any this_hash = |
65 |
|
9 |
signature::SignatureManager::MkFromFingerprint(blacklist[i]); |
66 |
✓✓ |
9 |
if (this_hash.IsNull()) |
67 |
|
3 |
continue; |
68 |
|
|
|
69 |
|
6 |
shash::Algorithms algorithm = this_hash.algorithm; |
70 |
✓✗ |
6 |
if (this_hash == signature_manager_->HashCertificate(algorithm)) |
71 |
|
6 |
return kFailBlacklisted; |
72 |
|
|
} |
73 |
|
|
|
74 |
✓✗ |
26 |
for (unsigned i = 0; i < fingerprints_.size(); ++i) { |
75 |
|
26 |
shash::Algorithms algorithm = fingerprints_[i].algorithm; |
76 |
✓✗ |
26 |
if (signature_manager_->HashCertificate(algorithm) == fingerprints_[i]) { |
77 |
✗✓ |
26 |
if (verification_flags_ & kFlagVerifyCaChain) { |
78 |
|
|
bool retval = signature_manager_->VerifyCaChain(); |
79 |
|
|
if (!retval) |
80 |
|
|
return kFailBadCaChain; |
81 |
|
|
} |
82 |
|
26 |
return kFailOk; |
83 |
|
|
} |
84 |
|
|
} |
85 |
|
|
return kFailNotListed; |
86 |
|
|
} |
87 |
|
|
|
88 |
|
|
|
89 |
|
32 |
Failures Whitelist::Load(const std::string &base_url) { |
90 |
|
32 |
const bool probe_hosts = base_url == ""; |
91 |
|
|
bool retval_b; |
92 |
|
|
download::Failures retval_dl; |
93 |
|
|
whitelist::Failures retval_wl; |
94 |
|
|
|
95 |
|
32 |
Reset(); |
96 |
|
|
|
97 |
|
32 |
const string whitelist_url = base_url + string("/.cvmfswhitelist"); |
98 |
|
|
download::JobInfo download_whitelist(&whitelist_url, |
99 |
|
32 |
false, probe_hosts, NULL); |
100 |
|
32 |
retval_dl = download_manager_->Fetch(&download_whitelist); |
101 |
✗✓ |
32 |
if (retval_dl != download::kFailOk) |
102 |
|
|
return kFailLoad; |
103 |
|
32 |
plain_size_ = download_whitelist.destination_mem.pos; |
104 |
✗✓ |
32 |
if (plain_size_ == 0) |
105 |
|
|
return kFailEmpty; |
106 |
|
|
plain_buf_ = |
107 |
|
32 |
reinterpret_cast<unsigned char *>(download_whitelist.destination_mem.data); |
108 |
|
|
|
109 |
|
32 |
retval_wl = ParseWhitelist(plain_buf_, plain_size_); |
110 |
✗✓ |
32 |
if (retval_wl != kFailOk) |
111 |
|
|
return retval_wl; |
112 |
|
|
|
113 |
✓✗ |
32 |
if (verification_flags_ & kFlagVerifyRsa) { |
114 |
|
32 |
retval_b = signature_manager_->VerifyLetter(plain_buf_, plain_size_, true); |
115 |
✗✓ |
32 |
if (!retval_b) { |
116 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, "failed to verify repository whitelist"); |
117 |
|
|
return kFailBadSignature; |
118 |
|
|
} |
119 |
|
|
} |
120 |
|
|
|
121 |
✗✓ |
32 |
if (verification_flags_ & kFlagVerifyPkcs7) { |
122 |
|
|
// Load the separate whitelist pkcs7 structure |
123 |
|
|
const string whitelist_pkcs7_url = |
124 |
|
|
base_url + string("cvmfswhitelist.pkcs7"); |
125 |
|
|
download::JobInfo download_whitelist_pkcs7(&whitelist_pkcs7_url, false, |
126 |
|
|
probe_hosts, NULL); |
127 |
|
|
retval_dl = download_manager_->Fetch(&download_whitelist_pkcs7); |
128 |
|
|
if (retval_dl != download::kFailOk) |
129 |
|
|
return kFailLoadPkcs7; |
130 |
|
|
pkcs7_size_ = download_whitelist_pkcs7.destination_mem.pos; |
131 |
|
|
if (pkcs7_size_ == 0) |
132 |
|
|
return kFailEmptyPkcs7; |
133 |
|
|
pkcs7_buf_ = reinterpret_cast<unsigned char *> |
134 |
|
|
(download_whitelist_pkcs7.destination_mem.data); |
135 |
|
|
|
136 |
|
|
unsigned char *extracted_whitelist; |
137 |
|
|
unsigned extracted_whitelist_size; |
138 |
|
|
vector<string> alt_uris; |
139 |
|
|
retval_b = |
140 |
|
|
signature_manager_->VerifyPkcs7(pkcs7_buf_, pkcs7_size_, |
141 |
|
|
&extracted_whitelist, |
142 |
|
|
&extracted_whitelist_size, |
143 |
|
|
&alt_uris); |
144 |
|
|
if (!retval_b) { |
145 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, |
146 |
|
|
"failed to verify repository whitelist (pkcs#7): %s", |
147 |
|
|
signature_manager_->GetCryptoError().c_str()); |
148 |
|
|
return kFailBadPkcs7; |
149 |
|
|
} |
150 |
|
|
|
151 |
|
|
// Check for subject alternative name matching the repository name |
152 |
|
|
bool found_uri = false; |
153 |
|
|
for (unsigned i = 0; i < alt_uris.size(); ++i) { |
154 |
|
|
LogCvmfs(kLogSignature, kLogDebug, "found pkcs#7 signer uri %s", |
155 |
|
|
alt_uris[i].c_str()); |
156 |
|
|
if (alt_uris[i] == "cvmfs:" + fqrn_) { |
157 |
|
|
found_uri = true; |
158 |
|
|
break; |
159 |
|
|
} |
160 |
|
|
} |
161 |
|
|
if (!found_uri) { |
162 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, |
163 |
|
|
"failed to find whitelist signer with SAN/URI cvmfs:%s", |
164 |
|
|
fqrn_.c_str()); |
165 |
|
|
free(extracted_whitelist); |
166 |
|
|
return kFailBadSignaturePkcs7; |
167 |
|
|
} |
168 |
|
|
|
169 |
|
|
// Check once again the extracted whitelist |
170 |
|
|
Reset(); |
171 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, "Extracted pkcs#7 whitelist:\n%s", |
172 |
|
|
string(reinterpret_cast<char *>(extracted_whitelist), |
173 |
|
|
extracted_whitelist_size).c_str()); |
174 |
|
|
retval_wl = ParseWhitelist(extracted_whitelist, extracted_whitelist_size); |
175 |
|
|
if (retval_wl != kFailOk) { |
176 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, |
177 |
|
|
"failed to verify repository certificate against pkcs#7 " |
178 |
|
|
"whitelist"); |
179 |
|
|
return kFailMalformedPkcs7; |
180 |
|
|
} |
181 |
|
|
} |
182 |
|
|
|
183 |
|
32 |
status_ = kStAvailable; |
184 |
|
32 |
return kFailOk; |
185 |
|
|
} |
186 |
|
|
|
187 |
|
|
|
188 |
|
|
/** |
189 |
|
|
* Helps for the time being with whitelists valid until after Y2038 on 32 bit |
190 |
|
|
* systems. |
191 |
|
|
*/ |
192 |
|
36 |
bool Whitelist::IsBefore(time_t now, const struct tm &t_whitelist) { |
193 |
|
|
struct tm t_local; |
194 |
✗✓ |
36 |
if (gmtime_r(&now, &t_local) == NULL) |
195 |
|
|
return false; |
196 |
✓✓ |
36 |
if (t_local.tm_year < t_whitelist.tm_year) return true; |
197 |
✓✓ |
11 |
if (t_local.tm_year > t_whitelist.tm_year) return false; |
198 |
✓✓ |
10 |
if (t_local.tm_mon < t_whitelist.tm_mon) return true; |
199 |
✗✓ |
3 |
if (t_local.tm_mon > t_whitelist.tm_mon) return false; |
200 |
✗✓ |
3 |
if (t_local.tm_mday < t_whitelist.tm_mday) return true; |
201 |
✗✓ |
3 |
if (t_local.tm_mday > t_whitelist.tm_mday) return false; |
202 |
✓✓ |
3 |
if (t_local.tm_hour < t_whitelist.tm_hour) return true; |
203 |
|
1 |
return false; |
204 |
|
|
} |
205 |
|
|
|
206 |
|
|
|
207 |
|
38 |
Failures Whitelist::ParseWhitelist(const unsigned char *whitelist, |
208 |
|
|
const unsigned whitelist_size) |
209 |
|
|
{ |
210 |
|
38 |
time_t local_timestamp = time(NULL); |
211 |
|
38 |
string line; |
212 |
|
38 |
unsigned payload_bytes = 0; |
213 |
|
38 |
bool verify_pkcs7 = false; |
214 |
|
38 |
bool verify_cachain = false; |
215 |
|
|
|
216 |
|
|
// Check timestamp (UTC), ignore issue date (legacy) |
217 |
|
38 |
line = GetLineMem(reinterpret_cast<const char *>(whitelist), whitelist_size); |
218 |
✓✓ |
38 |
if (line.length() != 14) { |
219 |
|
1 |
LogCvmfs(kLogSignature, kLogDebug, "invalid timestamp format"); |
220 |
|
1 |
return kFailMalformed; |
221 |
|
|
} |
222 |
|
37 |
payload_bytes += 15; |
223 |
|
|
|
224 |
|
|
// Expiry date |
225 |
|
|
line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes, |
226 |
|
37 |
whitelist_size-payload_bytes); |
227 |
✓✓ |
37 |
if (line.length() != 15) { |
228 |
|
1 |
LogCvmfs(kLogSignature, kLogDebug, "invalid timestamp format"); |
229 |
|
1 |
return kFailMalformed; |
230 |
|
|
} |
231 |
|
|
struct tm tm_wl; |
232 |
|
36 |
memset(&tm_wl, 0, sizeof(struct tm)); |
233 |
|
36 |
tm_wl.tm_year = String2Int64(line.substr(1, 4))-1900; |
234 |
|
36 |
tm_wl.tm_mon = String2Int64(line.substr(5, 2)) - 1; |
235 |
|
36 |
tm_wl.tm_mday = String2Int64(line.substr(7, 2)); |
236 |
|
36 |
tm_wl.tm_hour = String2Int64(line.substr(9, 2)); |
237 |
|
36 |
tm_wl.tm_min = tm_wl.tm_sec = 0; // exact on hours level |
238 |
|
36 |
time_t timestamp = timegm(&tm_wl); |
239 |
|
|
LogCvmfs(kLogSignature, kLogDebug, |
240 |
|
|
"whitelist UTC expiry timestamp in localtime: %s", |
241 |
|
36 |
StringifyTime(timestamp, false).c_str()); |
242 |
|
|
LogCvmfs(kLogSignature, kLogDebug, "local time: %s", |
243 |
|
36 |
StringifyTime(local_timestamp, true).c_str()); |
244 |
|
|
// Makeshift solution to deal with whitelists valid after Y2038 on 32bit |
245 |
|
|
// machines. Still unclear how glibc is going to treat the problem. |
246 |
✓✓ |
36 |
if (!IsBefore(local_timestamp, tm_wl)) { |
247 |
|
|
LogCvmfs(kLogSignature, kLogDebug | kLogSyslogErr, |
248 |
|
2 |
"whitelist lifetime verification failed, expired"); |
249 |
|
2 |
return kFailExpired; |
250 |
|
|
} |
251 |
|
|
// if (timestamp < 0) { |
252 |
|
|
// LogCvmfs(kLogSignature, kLogDebug, "invalid timestamp"); |
253 |
|
|
// return kFailMalformed; |
254 |
|
|
// } |
255 |
|
|
// if (local_timestamp > timestamp) { |
256 |
|
|
// LogCvmfs(kLogSignature, kLogDebug | kLogSyslogErr, |
257 |
|
|
// "whitelist lifetime verification failed, expired"); |
258 |
|
|
// return kFailExpired; |
259 |
|
|
// } |
260 |
|
34 |
expires_ = timestamp; |
261 |
|
34 |
payload_bytes += 16; |
262 |
|
|
|
263 |
|
|
// Check repository name |
264 |
|
|
line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes, |
265 |
|
34 |
whitelist_size-payload_bytes); |
266 |
✓✗✓✓ ✓✗✗✗ ✗✓✓ |
34 |
if ((fqrn_ != "") && ("N" + fqrn_ != line)) { |
267 |
|
|
LogCvmfs(kLogSignature, kLogDebug, |
268 |
|
|
"repository name on the whitelist does not match " |
269 |
|
|
"(found %s, expected %s)", |
270 |
|
1 |
line.c_str(), fqrn_.c_str()); |
271 |
|
1 |
return kFailNameMismatch; |
272 |
|
|
} |
273 |
|
33 |
payload_bytes += line.length() + 1; |
274 |
|
|
|
275 |
|
|
// Check for PKCS7 |
276 |
|
|
line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes, |
277 |
|
33 |
whitelist_size-payload_bytes); |
278 |
✗✓ |
33 |
if (line == "Vpkcs7") { |
279 |
|
|
LogCvmfs(kLogSignature, kLogDebug, "whitelist verification: pkcs#7"); |
280 |
|
|
verify_pkcs7 = true; |
281 |
|
|
payload_bytes += line.length() + 1; |
282 |
|
|
line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes, |
283 |
|
|
whitelist_size-payload_bytes); |
284 |
|
|
} |
285 |
|
|
|
286 |
|
|
// Check for CA chain verification |
287 |
|
|
line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes, |
288 |
|
33 |
whitelist_size-payload_bytes); |
289 |
✗✓ |
33 |
if (line == "Wcachain") { |
290 |
|
|
LogCvmfs(kLogSignature, kLogDebug, |
291 |
|
|
"whitelist imposes ca chain verification of manifest signature"); |
292 |
|
|
verify_cachain = true; |
293 |
|
|
payload_bytes += line.length() + 1; |
294 |
|
|
line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes, |
295 |
|
|
whitelist_size-payload_bytes); |
296 |
|
|
} |
297 |
|
|
|
298 |
✓✓ |
33 |
do { |
299 |
✓✓ |
65 |
if (line == "--") break; |
300 |
|
33 |
shash::Any this_hash = signature::SignatureManager::MkFromFingerprint(line); |
301 |
✓✓ |
33 |
if (!this_hash.IsNull()) |
302 |
|
32 |
fingerprints_.push_back(this_hash); |
303 |
|
|
|
304 |
|
33 |
payload_bytes += line.length() + 1; |
305 |
|
|
line = GetLineMem(reinterpret_cast<const char *>(whitelist)+payload_bytes, |
306 |
|
33 |
whitelist_size-payload_bytes); |
307 |
|
|
} while (payload_bytes < whitelist_size); |
308 |
|
|
|
309 |
✗✓ |
33 |
verification_flags_ = verify_pkcs7 ? kFlagVerifyPkcs7 : kFlagVerifyRsa; |
310 |
✗✓ |
33 |
if (verify_cachain) |
311 |
|
|
verification_flags_ |= kFlagVerifyCaChain; |
312 |
|
33 |
return kFailOk; |
313 |
|
|
} |
314 |
|
|
|
315 |
|
|
|
316 |
|
111 |
void Whitelist::Reset() { |
317 |
|
111 |
status_ = kStNone; |
318 |
|
111 |
fingerprints_.clear(); |
319 |
|
111 |
expires_ = 0; |
320 |
|
111 |
verification_flags_ = 0; |
321 |
✓✓ |
111 |
if (plain_buf_) |
322 |
|
32 |
free(plain_buf_); |
323 |
✗✓ |
111 |
if (pkcs7_buf_) |
324 |
|
|
free(pkcs7_buf_); |
325 |
|
111 |
plain_buf_ = NULL; |
326 |
|
111 |
pkcs7_buf_ = NULL; |
327 |
|
111 |
plain_size_ = 0; |
328 |
|
111 |
pkcs7_size_ = 0; |
329 |
|
111 |
} |
330 |
|
|
|
331 |
|
|
|
332 |
|
39 |
Whitelist::Whitelist(const string &fqrn, |
333 |
|
|
download::DownloadManager *download_manager, |
334 |
|
|
signature::SignatureManager *signature_manager) : |
335 |
|
|
fqrn_(fqrn), |
336 |
|
|
download_manager_(download_manager), |
337 |
|
|
signature_manager_(signature_manager), |
338 |
|
|
plain_buf_(NULL), |
339 |
|
|
plain_size_(0), |
340 |
|
|
pkcs7_buf_(NULL), |
341 |
|
39 |
pkcs7_size_(0) |
342 |
|
|
{ |
343 |
|
39 |
Reset(); |
344 |
|
|
} |
345 |
|
|
|
346 |
|
|
|
347 |
|
|
Whitelist::Whitelist(const Whitelist &other) : |
348 |
|
|
fqrn_(other.fqrn_), |
349 |
|
|
download_manager_(other.download_manager_), |
350 |
|
|
signature_manager_(other.signature_manager_), |
351 |
|
|
status_(other.status_), |
352 |
|
|
fingerprints_(other.fingerprints_), |
353 |
|
|
expires_(other.expires_), |
354 |
|
|
verification_flags_(other.verification_flags_) |
355 |
|
|
{ |
356 |
|
|
other.CopyBuffers(&plain_size_, &plain_buf_, &pkcs7_size_, &pkcs7_buf_); |
357 |
|
|
} |
358 |
|
|
|
359 |
|
|
|
360 |
|
|
// Testing only |
361 |
|
1 |
Whitelist::Whitelist() |
362 |
|
|
: download_manager_(NULL) |
363 |
|
|
, signature_manager_(NULL) |
364 |
|
|
, status_(kStNone) |
365 |
|
|
, expires_(0) |
366 |
|
|
, verification_flags_(0) |
367 |
|
|
, plain_buf_(NULL) |
368 |
|
|
, plain_size_(0) |
369 |
|
|
, pkcs7_buf_(NULL) |
370 |
|
1 |
, pkcs7_size_(0) |
371 |
|
|
{ |
372 |
|
1 |
} |
373 |
|
|
|
374 |
|
|
Whitelist &Whitelist::operator= (const Whitelist &other) { |
375 |
|
|
if (&other == this) |
376 |
|
|
return *this; |
377 |
|
|
|
378 |
|
|
Reset(); |
379 |
|
|
fqrn_ = other.fqrn_; |
380 |
|
|
download_manager_ = other.download_manager_; |
381 |
|
|
signature_manager_ = other.signature_manager_; |
382 |
|
|
status_ = other.status_; |
383 |
|
|
fingerprints_ = other.fingerprints_; |
384 |
|
|
expires_ = other.expires_; |
385 |
|
|
verification_flags_ = other.verification_flags_; |
386 |
|
|
other.CopyBuffers(&plain_size_, &plain_buf_, &pkcs7_size_, &pkcs7_buf_); |
387 |
|
|
|
388 |
|
|
return *this; |
389 |
|
|
} |
390 |
|
|
|
391 |
|
|
|
392 |
|
40 |
Whitelist::~Whitelist() { |
393 |
|
40 |
Reset(); |
394 |
|
|
} |
395 |
|
|
|
396 |
|
|
} // namespace whitelist |