/src/rnp/src/librekey/key_store_kbx.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2017-2023, [Ribose Inc](https://www.ribose.com). |
3 | | * All rights reserved. |
4 | | * |
5 | | * Redistribution and use in source and binary forms, with or without |
6 | | * modification, are permitted provided that the following conditions |
7 | | * are met: |
8 | | * 1. Redistributions of source code must retain the above copyright |
9 | | * notice, this list of conditions and the following disclaimer. |
10 | | * 2. Redistributions in binary form must reproduce the above copyright |
11 | | * notice, this list of conditions and the following disclaimer in the |
12 | | * documentation and/or other materials provided with the distribution. |
13 | | * |
14 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
15 | | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
16 | | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS |
18 | | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
19 | | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
20 | | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
21 | | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
22 | | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
23 | | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
24 | | * POSSIBILITY OF SUCH DAMAGE. |
25 | | */ |
26 | | |
27 | | #include <sys/types.h> |
28 | | #ifdef HAVE_SYS_PARAM_H |
29 | | #include <sys/param.h> |
30 | | #else |
31 | | #include "uniwin.h" |
32 | | #endif |
33 | | #include <string.h> |
34 | | #include <stdint.h> |
35 | | #include <time.h> |
36 | | #ifndef __STDC_FORMAT_MACROS |
37 | | #define __STDC_FORMAT_MACROS |
38 | | #endif |
39 | | #include <cinttypes> |
40 | | #include <cassert> |
41 | | |
42 | | #include "key.hpp" |
43 | | #include "kbx_blob.hpp" |
44 | | #include "rekey/rnp_key_store.h" |
45 | | #include <librepgp/stream-sig.h> |
46 | | |
47 | | /* same limit with GnuPG 2.1 */ |
48 | 186k | #define BLOB_SIZE_LIMIT (5 * 1024 * 1024) |
49 | | /* limit the number of keys/sigs/uids in the blob */ |
50 | 207k | #define BLOB_OBJ_LIMIT 0x8000 |
51 | | |
52 | 420k | #define BLOB_HEADER_SIZE 0x5 |
53 | 13.4k | #define BLOB_FIRST_SIZE 0x20 |
54 | 158k | #define BLOB_KEY_SIZE 0x1C |
55 | 98.1k | #define BLOB_UID_SIZE 0x0C |
56 | 820k | #define BLOB_SIG_SIZE 0x04 |
57 | 68.8k | #define BLOB_VALIDITY_SIZE 0x10 |
58 | | |
59 | | uint8_t |
60 | | kbx_blob_t::ru8(size_t idx) |
61 | 331k | { |
62 | 331k | return image_[idx]; |
63 | 331k | } |
64 | | |
65 | | uint16_t |
66 | | kbx_blob_t::ru16(size_t idx) |
67 | 674k | { |
68 | 674k | return read_uint16(image_.data() + idx); |
69 | 674k | } |
70 | | |
71 | | uint32_t |
72 | | kbx_blob_t::ru32(size_t idx) |
73 | 1.25M | { |
74 | 1.25M | return read_uint32(image_.data() + idx); |
75 | 1.25M | } |
76 | | |
77 | | kbx_blob_t::kbx_blob_t(std::vector<uint8_t> &data) |
78 | 92.4k | { |
79 | 92.4k | if (data.size() < BLOB_HEADER_SIZE) { |
80 | 0 | RNP_LOG("Too small KBX blob."); |
81 | 0 | throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); |
82 | 0 | } |
83 | 92.4k | uint32_t len = read_uint32(data.data()); |
84 | 92.4k | if (len > BLOB_SIZE_LIMIT) { |
85 | 0 | RNP_LOG("Too large KBX blob."); |
86 | 0 | throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); |
87 | 0 | } |
88 | 92.4k | if (len != data.size()) { |
89 | 0 | RNP_LOG("KBX blob size mismatch."); |
90 | 0 | throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); |
91 | 0 | } |
92 | 92.4k | image_ = data; |
93 | 92.4k | type_ = (kbx_blob_type_t) ru8(4); |
94 | 92.4k | } |
95 | | |
96 | | bool |
97 | | kbx_header_blob_t::parse() |
98 | 3.12k | { |
99 | 3.12k | if (length() != BLOB_FIRST_SIZE) { |
100 | 21 | RNP_LOG("The first blob has wrong length: %" PRIu32 " but expected %d", |
101 | 0 | length(), |
102 | 0 | (int) BLOB_FIRST_SIZE); |
103 | 0 | return false; |
104 | 21 | } |
105 | | |
106 | 3.10k | size_t idx = BLOB_HEADER_SIZE; |
107 | 3.10k | version_ = ru8(idx++); |
108 | 3.10k | if (version_ != 1) { |
109 | 11 | RNP_LOG("Wrong version, expect 1 but has %" PRIu8, version_); |
110 | 0 | return false; |
111 | 11 | } |
112 | | |
113 | 3.09k | flags_ = ru16(idx); |
114 | 3.09k | idx += 2; |
115 | | |
116 | | // blob should contains a magic KBXf |
117 | 3.09k | if (memcmp(image_.data() + idx, "KBXf", 4)) { |
118 | 33 | RNP_LOG("The first blob hasn't got a KBXf magic string"); |
119 | 0 | return false; |
120 | 33 | } |
121 | 3.05k | idx += 4; |
122 | | // RFU |
123 | 3.05k | idx += 4; |
124 | | // File creation time |
125 | 3.05k | file_created_at_ = ru32(idx); |
126 | 3.05k | idx += 4; |
127 | | // Duplicated? |
128 | 3.05k | file_created_at_ = ru32(idx); |
129 | | // RFU +4 bytes |
130 | | // RFU +4 bytes |
131 | 3.05k | return true; |
132 | 3.09k | } |
133 | | |
134 | | bool |
135 | | kbx_pgp_blob_t::parse() |
136 | 69.2k | { |
137 | | /* Skip parsing of X.509 and empty blobs. */ |
138 | 69.2k | if (type_ != KBX_PGP_BLOB) { |
139 | 0 | return true; |
140 | 0 | } |
141 | 69.2k | if (image_.size() < 15 + BLOB_HEADER_SIZE) { |
142 | 6 | RNP_LOG("Too few data in the blob."); |
143 | 0 | return false; |
144 | 6 | } |
145 | | |
146 | 69.2k | size_t idx = BLOB_HEADER_SIZE; |
147 | | /* version */ |
148 | 69.2k | version_ = ru8(idx++); |
149 | 69.2k | if (version_ != 1) { |
150 | 13 | RNP_LOG("Wrong version: %" PRIu8, version_); |
151 | 0 | return false; |
152 | 13 | } |
153 | | /* flags */ |
154 | 69.2k | flags_ = ru16(idx); |
155 | 69.2k | idx += 2; |
156 | | /* keyblock offset */ |
157 | 69.2k | keyblock_offset_ = ru32(idx); |
158 | 69.2k | idx += 4; |
159 | | /* keyblock length */ |
160 | 69.2k | keyblock_length_ = ru32(idx); |
161 | 69.2k | idx += 4; |
162 | | |
163 | 69.2k | if ((keyblock_offset_ > image_.size()) || |
164 | 69.2k | (keyblock_offset_ > (UINT32_MAX - keyblock_length_)) || |
165 | 69.2k | (image_.size() < (keyblock_offset_ + keyblock_length_))) { |
166 | 90 | RNP_LOG("Wrong keyblock offset/length, blob size: %zu" |
167 | 0 | ", keyblock offset: %" PRIu32 ", length: %" PRIu32, |
168 | 0 | image_.size(), |
169 | 0 | keyblock_offset_, |
170 | 0 | keyblock_length_); |
171 | 0 | return false; |
172 | 90 | } |
173 | | /* number of key blocks */ |
174 | 69.1k | size_t nkeys = ru16(idx); |
175 | 69.1k | idx += 2; |
176 | 69.1k | if (nkeys < 1) { |
177 | 7 | RNP_LOG("PGP blob should contain at least 1 key"); |
178 | 0 | return false; |
179 | 7 | } |
180 | 69.1k | if (nkeys > BLOB_OBJ_LIMIT) { |
181 | 7 | RNP_LOG("Too many keys in the PGP blob"); |
182 | 0 | return false; |
183 | 7 | } |
184 | | |
185 | | /* Size of the single key record */ |
186 | 69.1k | size_t keys_len = ru16(idx); |
187 | 69.1k | idx += 2; |
188 | 69.1k | if (keys_len < BLOB_KEY_SIZE) { |
189 | 9 | RNP_LOG( |
190 | 0 | "Key record needs %d bytes, but contains: %zu bytes", (int) BLOB_KEY_SIZE, keys_len); |
191 | 0 | return false; |
192 | 9 | } |
193 | | |
194 | 158k | for (size_t i = 0; i < nkeys; i++) { |
195 | 89.7k | if (image_.size() - idx < keys_len) { |
196 | 51 | RNP_LOG("Too few bytes left for key blob"); |
197 | 0 | return false; |
198 | 51 | } |
199 | | |
200 | 89.6k | kbx_pgp_key_t nkey = {}; |
201 | | /* copy fingerprint */ |
202 | 89.6k | memcpy(nkey.fp, &image_[idx], 20); |
203 | 89.6k | idx += 20; |
204 | | /* keyid offset */ |
205 | 89.6k | nkey.keyid_offset = ru32(idx); |
206 | 89.6k | idx += 4; |
207 | | /* flags */ |
208 | 89.6k | nkey.flags = ru16(idx); |
209 | 89.6k | idx += 2; |
210 | | /* RFU */ |
211 | 89.6k | idx += 2; |
212 | | /* skip padding bytes if it existed */ |
213 | 89.6k | idx += keys_len - BLOB_KEY_SIZE; |
214 | 89.6k | keys_.push_back(std::move(nkey)); |
215 | 89.6k | } |
216 | | |
217 | 69.1k | if (image_.size() - idx < 2) { |
218 | 2 | RNP_LOG("No data for sn_size"); |
219 | 0 | return false; |
220 | 2 | } |
221 | 69.0k | size_t sn_size = ru16(idx); |
222 | 69.0k | idx += 2; |
223 | | |
224 | 69.0k | if (image_.size() - idx < sn_size) { |
225 | 40 | RNP_LOG("SN is %zu, while bytes left are %zu", sn_size, image_.size() - idx); |
226 | 0 | return false; |
227 | 40 | } |
228 | | |
229 | 69.0k | if (sn_size) { |
230 | 1.02k | sn_ = {image_.begin() + idx, image_.begin() + idx + sn_size}; |
231 | 1.02k | idx += sn_size; |
232 | 1.02k | } |
233 | | |
234 | 69.0k | if (image_.size() - idx < 4) { |
235 | 9 | RNP_LOG("Too few data for uids"); |
236 | 0 | return false; |
237 | 9 | } |
238 | 69.0k | size_t nuids = ru16(idx); |
239 | 69.0k | if (nuids > BLOB_OBJ_LIMIT) { |
240 | 12 | RNP_LOG("Too many uids in the PGP blob"); |
241 | 0 | return false; |
242 | 12 | } |
243 | | |
244 | 69.0k | size_t uids_len = ru16(idx + 2); |
245 | 69.0k | idx += 4; |
246 | | |
247 | 69.0k | if (uids_len < BLOB_UID_SIZE) { |
248 | 17 | RNP_LOG("Too few bytes for uid struct: %zu", uids_len); |
249 | 0 | return false; |
250 | 17 | } |
251 | | |
252 | 98.1k | for (size_t i = 0; i < nuids; i++) { |
253 | 29.1k | if (image_.size() - idx < uids_len) { |
254 | 58 | RNP_LOG("Too few bytes to read uid struct."); |
255 | 0 | return false; |
256 | 58 | } |
257 | 29.0k | kbx_pgp_uid_t nuid = {}; |
258 | | /* offset */ |
259 | 29.0k | nuid.offset = ru32(idx); |
260 | 29.0k | idx += 4; |
261 | | /* length */ |
262 | 29.0k | nuid.length = ru32(idx); |
263 | 29.0k | idx += 4; |
264 | | /* flags */ |
265 | 29.0k | nuid.flags = ru16(idx); |
266 | 29.0k | idx += 2; |
267 | | /* validity */ |
268 | 29.0k | nuid.validity = ru8(idx); |
269 | 29.0k | idx++; |
270 | | /* RFU */ |
271 | 29.0k | idx++; |
272 | | // skip padding bytes if it existed |
273 | 29.0k | idx += uids_len - BLOB_UID_SIZE; |
274 | | |
275 | 29.0k | uids_.push_back(std::move(nuid)); |
276 | 29.0k | } |
277 | | |
278 | 68.9k | if (image_.size() - idx < 4) { |
279 | 4 | RNP_LOG("No data left for sigs"); |
280 | 0 | return false; |
281 | 4 | } |
282 | | |
283 | 68.9k | size_t nsigs = ru16(idx); |
284 | 68.9k | if (nsigs > BLOB_OBJ_LIMIT) { |
285 | 5 | RNP_LOG("Too many sigs in the PGP blob"); |
286 | 0 | return false; |
287 | 5 | } |
288 | | |
289 | 68.9k | size_t sigs_len = ru16(idx + 2); |
290 | 68.9k | idx += 4; |
291 | | |
292 | 68.9k | if (sigs_len < BLOB_SIG_SIZE) { |
293 | 7 | RNP_LOG("Too small SIGN structure: %zu", sigs_len); |
294 | 0 | return false; |
295 | 7 | } |
296 | | |
297 | 820k | for (size_t i = 0; i < nsigs; i++) { |
298 | 751k | if (image_.size() - idx < sigs_len) { |
299 | 72 | RNP_LOG("Too few data for sig"); |
300 | 0 | return false; |
301 | 72 | } |
302 | | |
303 | 751k | kbx_pgp_sig_t nsig = {}; |
304 | 751k | nsig.expired = ru32(idx); |
305 | 751k | idx += 4; |
306 | | |
307 | | // skip padding bytes if it existed |
308 | 751k | idx += (sigs_len - BLOB_SIG_SIZE); |
309 | | |
310 | 751k | sigs_.push_back(nsig); |
311 | 751k | } |
312 | | |
313 | 68.8k | if (image_.size() - idx < BLOB_VALIDITY_SIZE) { |
314 | 6 | RNP_LOG("Too few data for trust/validities"); |
315 | 0 | return false; |
316 | 6 | } |
317 | | |
318 | 68.8k | ownertrust_ = ru8(idx); |
319 | 68.8k | idx++; |
320 | 68.8k | all_validity_ = ru8(idx); |
321 | 68.8k | idx++; |
322 | | // RFU |
323 | 68.8k | idx += 2; |
324 | 68.8k | recheck_after_ = ru32(idx); |
325 | 68.8k | idx += 4; |
326 | 68.8k | latest_timestamp_ = ru32(idx); |
327 | 68.8k | idx += 4; |
328 | 68.8k | blob_created_at_ = ru32(idx); |
329 | | // do not forget to idx += 4 on further expansion |
330 | | |
331 | | // here starts keyblock, UID and reserved space for future usage |
332 | | |
333 | | // Maybe we should add checksum verify but GnuPG never checked it |
334 | | // Checksum is last 20 bytes of blob and it is SHA-1, if it invalid MD5 and starts from 4 |
335 | | // zero it is MD5. |
336 | | |
337 | 68.8k | return true; |
338 | 68.8k | } |
339 | | |
340 | | namespace rnp { |
341 | | namespace { |
342 | | std::unique_ptr<kbx_blob_t> |
343 | | kbx_parse_blob(const uint8_t *image, size_t image_len) |
344 | 92.5k | { |
345 | 92.5k | std::unique_ptr<kbx_blob_t> blob; |
346 | | // a blob shouldn't be less of length + type |
347 | 92.5k | if (image_len < BLOB_HEADER_SIZE) { |
348 | 0 | RNP_LOG("Blob size is %zu but it shouldn't be less of header", image_len); |
349 | 0 | return blob; |
350 | 0 | } |
351 | | |
352 | 92.5k | try { |
353 | 92.5k | std::vector<uint8_t> data(image, image + image_len); |
354 | 92.5k | kbx_blob_type_t type = (kbx_blob_type_t) image[4]; |
355 | | |
356 | 92.5k | switch (type) { |
357 | 13.6k | case KBX_EMPTY_BLOB: |
358 | 13.6k | blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data)); |
359 | 13.6k | break; |
360 | 3.12k | case KBX_HEADER_BLOB: |
361 | 3.12k | blob = std::unique_ptr<kbx_blob_t>(new kbx_header_blob_t(data)); |
362 | 3.12k | break; |
363 | 69.2k | case KBX_PGP_BLOB: |
364 | 69.2k | blob = std::unique_ptr<kbx_blob_t>(new kbx_pgp_blob_t(data)); |
365 | 69.2k | break; |
366 | 6.45k | case KBX_X509_BLOB: |
367 | | // current we doesn't parse X509 blob, so, keep it as is |
368 | 6.45k | blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data)); |
369 | 6.45k | break; |
370 | | // unsupported blob type |
371 | 13 | default: |
372 | 13 | RNP_LOG("Unsupported blob type: %d", (int) type); |
373 | 0 | return blob; |
374 | 92.5k | } |
375 | | |
376 | 92.4k | if (!blob->parse()) { |
377 | 480 | return NULL; |
378 | 480 | } |
379 | 92.4k | } catch (const std::exception &e) { |
380 | | /* LCOV_EXCL_START */ |
381 | 0 | RNP_LOG("%s", e.what()); |
382 | 0 | return NULL; |
383 | | /* LCOV_EXCL_END */ |
384 | 0 | } |
385 | 92.0k | return blob; |
386 | 92.5k | } |
387 | | } // namespace |
388 | | |
389 | | bool |
390 | | KeyStore::load_kbx(pgp_source_t &src, const KeyProvider *key_provider) |
391 | 10.3k | { |
392 | 10.3k | try { |
393 | 10.3k | MemorySource mem(src); |
394 | 10.3k | size_t has_bytes = mem.size(); |
395 | 10.3k | uint8_t * buf = (uint8_t *) mem.memory(); |
396 | | |
397 | 10.3k | if (has_bytes < BLOB_FIRST_SIZE) { |
398 | 14 | RNP_LOG("Too few bytes for valid KBX"); |
399 | 0 | return false; |
400 | 14 | } |
401 | 99.1k | while (has_bytes > 4) { |
402 | 94.1k | size_t blob_length = read_uint32(buf); |
403 | 94.1k | if (blob_length > BLOB_SIZE_LIMIT) { |
404 | 774 | RNP_LOG("Blob size is %zu bytes but limit is %d bytes", |
405 | 0 | blob_length, |
406 | 0 | (int) BLOB_SIZE_LIMIT); |
407 | 0 | return false; |
408 | 774 | } |
409 | 93.3k | if (blob_length < BLOB_HEADER_SIZE) { |
410 | 97 | RNP_LOG("Too small blob header size"); |
411 | 0 | return false; |
412 | 97 | } |
413 | 93.2k | if (has_bytes < blob_length) { |
414 | 750 | RNP_LOG("Blob have size %zu bytes but file contains only %zu bytes", |
415 | 0 | blob_length, |
416 | 0 | has_bytes); |
417 | 0 | return false; |
418 | 750 | } |
419 | 92.5k | auto blob = kbx_parse_blob(buf, blob_length); |
420 | 92.5k | if (!blob.get()) { |
421 | 493 | RNP_LOG("Failed to parse blob"); |
422 | 0 | return false; |
423 | 493 | } |
424 | 92.0k | kbx_blob_t *pblob = blob.get(); |
425 | 92.0k | blobs.push_back(std::move(blob)); |
426 | | |
427 | 92.0k | if (pblob->type() == KBX_PGP_BLOB) { |
428 | | // parse keyblock if it existed |
429 | 68.8k | kbx_pgp_blob_t &pgp_blob = dynamic_cast<kbx_pgp_blob_t &>(*pblob); |
430 | 68.8k | if (!pgp_blob.keyblock_length()) { |
431 | 1 | RNP_LOG("PGP blob have zero size"); |
432 | 0 | return false; |
433 | 1 | } |
434 | | |
435 | 68.8k | MemorySource blsrc(pgp_blob.image().data() + pgp_blob.keyblock_offset(), |
436 | 68.8k | pgp_blob.keyblock_length(), |
437 | 68.8k | false); |
438 | 68.8k | if (load_pgp(blsrc.src())) { |
439 | 3.14k | return false; |
440 | 3.14k | } |
441 | 68.8k | } |
442 | | |
443 | 88.8k | has_bytes -= blob_length; |
444 | 88.8k | buf += blob_length; |
445 | 88.8k | } |
446 | 5.04k | if (has_bytes) { |
447 | 1.17k | RNP_LOG("KBX source has excess trailing bytes"); |
448 | 1.17k | } |
449 | 0 | return true; |
450 | 10.2k | } catch (const std::exception &e) { |
451 | | /* LCOV_EXCL_START */ |
452 | 0 | RNP_LOG("%s", e.what()); |
453 | 0 | return false; |
454 | | /* LCOV_EXCL_END */ |
455 | 0 | } |
456 | 10.3k | } |
457 | | |
458 | | namespace { |
459 | | bool |
460 | | pbuf(pgp_dest_t &dst, const void *buf, size_t len) |
461 | 0 | { |
462 | 0 | dst_write(&dst, buf, len); |
463 | 0 | return dst.werr == RNP_SUCCESS; |
464 | 0 | } |
465 | | |
466 | | bool |
467 | | pu8(pgp_dest_t &dst, uint8_t p) |
468 | 0 | { |
469 | 0 | return pbuf(dst, &p, 1); |
470 | 0 | } |
471 | | |
472 | | bool |
473 | | pu16(pgp_dest_t &dst, uint16_t f) |
474 | 0 | { |
475 | 0 | uint8_t p[2]; |
476 | 0 | p[0] = (uint8_t)(f >> 8); |
477 | 0 | p[1] = (uint8_t) f; |
478 | 0 | return pbuf(dst, p, 2); |
479 | 0 | } |
480 | | |
481 | | bool |
482 | | pu32(pgp_dest_t &dst, uint32_t f) |
483 | 0 | { |
484 | 0 | uint8_t p[4]; |
485 | 0 | write_uint32(p, f); |
486 | 0 | return pbuf(dst, p, 4); |
487 | 0 | } |
488 | | |
489 | | bool |
490 | | kbx_write_header(const KeyStore &key_store, pgp_dest_t &dst) |
491 | 0 | { |
492 | 0 | uint16_t flags = 0; |
493 | 0 | uint32_t file_created_at = key_store.secctx.time(); |
494 | |
|
495 | 0 | if (!key_store.blobs.empty() && (key_store.blobs[0]->type() == KBX_HEADER_BLOB)) { |
496 | 0 | kbx_header_blob_t &blob = dynamic_cast<kbx_header_blob_t &>(*key_store.blobs[0]); |
497 | 0 | file_created_at = blob.file_created_at(); |
498 | 0 | } |
499 | |
|
500 | 0 | return !(!pu32(dst, BLOB_FIRST_SIZE) || !pu8(dst, KBX_HEADER_BLOB) || |
501 | 0 | !pu8(dst, 1) // version |
502 | 0 | || !pu16(dst, flags) || !pbuf(dst, "KBXf", 4) || !pu32(dst, 0) // RFU |
503 | 0 | || !pu32(dst, 0) // RFU |
504 | 0 | || !pu32(dst, file_created_at) || !pu32(dst, key_store.secctx.time()) || |
505 | 0 | !pu32(dst, 0)); // RFU |
506 | 0 | } |
507 | | |
508 | | bool |
509 | | kbx_write_pgp(const KeyStore &key_store, const Key &key, pgp_dest_t &dst) |
510 | 0 | { |
511 | 0 | MemoryDest mem(NULL, BLOB_SIZE_LIMIT); |
512 | |
|
513 | 0 | if (!pu32(mem.dst(), 0)) { // length, we don't know length of blob yet, so it's 0 |
514 | 0 | return false; |
515 | 0 | } |
516 | | |
517 | 0 | if (!pu8(mem.dst(), KBX_PGP_BLOB) || !pu8(mem.dst(), 1)) { // type, version |
518 | 0 | return false; |
519 | 0 | } |
520 | | |
521 | 0 | if (!pu16(mem.dst(), 0)) { // flags, not used by GnuPG |
522 | 0 | return false; |
523 | 0 | } |
524 | | |
525 | 0 | if (!pu32(mem.dst(), 0) || |
526 | 0 | !pu32(mem.dst(), 0)) { // offset and length of keyblock, update later |
527 | 0 | return false; |
528 | 0 | } |
529 | | |
530 | 0 | if (!pu16(mem.dst(), 1 + key.subkey_count())) { // number of keys in keyblock |
531 | 0 | return false; |
532 | 0 | } |
533 | 0 | if (!pu16(mem.dst(), 28)) { // size of key info structure) |
534 | 0 | return false; |
535 | 0 | } |
536 | | |
537 | 0 | if (!pbuf(mem.dst(), key.fp().data(), key.fp().size()) || |
538 | 0 | !pu32(mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) |
539 | 0 | !pu16(mem.dst(), 0) || // flags, not used by GnuPG |
540 | 0 | !pu16(mem.dst(), 0)) { // RFU |
541 | 0 | return false; |
542 | 0 | } |
543 | | |
544 | | // same as above, for each subkey |
545 | 0 | std::vector<uint32_t> subkey_sig_expirations; |
546 | 0 | for (auto &sfp : key.subkey_fps()) { |
547 | 0 | auto *subkey = key_store.get_key(sfp); |
548 | 0 | if (!subkey || !pbuf(mem.dst(), subkey->fp().data(), subkey->fp().size()) || |
549 | 0 | !pu32(mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) |
550 | 0 | !pu16(mem.dst(), 0) || // flags, not used by GnuPG |
551 | 0 | !pu16(mem.dst(), 0)) { // RFU |
552 | 0 | return false; |
553 | 0 | } |
554 | | // load signature expirations while we're at it |
555 | 0 | for (size_t i = 0; i < subkey->sig_count(); i++) { |
556 | 0 | uint32_t expiration = subkey->get_sig(i).sig.key_expiration(); |
557 | 0 | subkey_sig_expirations.push_back(expiration); |
558 | 0 | } |
559 | 0 | } |
560 | | |
561 | 0 | if (!pu16(mem.dst(), 0)) { // Zero size of serial number |
562 | 0 | return false; |
563 | 0 | } |
564 | | |
565 | | // skip serial number |
566 | 0 | if (!pu16(mem.dst(), key.uid_count()) || !pu16(mem.dst(), 12)) { |
567 | 0 | return false; |
568 | 0 | } |
569 | | |
570 | 0 | size_t uid_start = mem.writeb(); |
571 | 0 | for (size_t i = 0; i < key.uid_count(); i++) { |
572 | 0 | if (!pu32(mem.dst(), 0) || |
573 | 0 | !pu32(mem.dst(), 0)) { // UID offset and length, update when blob has done |
574 | 0 | return false; |
575 | 0 | } |
576 | | |
577 | 0 | if (!pu16(mem.dst(), 0)) { // flags, (not yet used) |
578 | 0 | return false; |
579 | 0 | } |
580 | | |
581 | 0 | if (!pu8(mem.dst(), 0) || !pu8(mem.dst(), 0)) { // Validity & RFU |
582 | 0 | return false; |
583 | 0 | } |
584 | 0 | } |
585 | | |
586 | 0 | if (!pu16(mem.dst(), key.sig_count() + subkey_sig_expirations.size()) || |
587 | 0 | !pu16(mem.dst(), 4)) { |
588 | 0 | return false; |
589 | 0 | } |
590 | | |
591 | 0 | for (size_t i = 0; i < key.sig_count(); i++) { |
592 | 0 | if (!pu32(mem.dst(), key.get_sig(i).sig.key_expiration())) { |
593 | 0 | return false; |
594 | 0 | } |
595 | 0 | } |
596 | 0 | for (auto &expiration : subkey_sig_expirations) { |
597 | 0 | if (!pu32(mem.dst(), expiration)) { |
598 | 0 | return false; |
599 | 0 | } |
600 | 0 | } |
601 | | |
602 | 0 | if (!pu8(mem.dst(), 0) || |
603 | 0 | !pu8(mem.dst(), 0)) { // Assigned ownertrust & All_Validity (not yet used) |
604 | 0 | return false; |
605 | 0 | } |
606 | | |
607 | 0 | if (!pu16(mem.dst(), 0) || !pu32(mem.dst(), 0)) { // RFU & Recheck_after |
608 | 0 | return false; |
609 | 0 | } |
610 | | |
611 | 0 | if (!pu32(mem.dst(), key_store.secctx.time()) || |
612 | 0 | !pu32(mem.dst(), key_store.secctx.time())) { // Latest timestamp && created |
613 | 0 | return false; |
614 | 0 | } |
615 | | |
616 | 0 | if (!pu32(mem.dst(), 0)) { // Size of reserved space |
617 | 0 | return false; |
618 | 0 | } |
619 | | |
620 | | // wrtite UID, we might redesign PGP write and use this information from keyblob |
621 | 0 | for (size_t i = 0; i < key.uid_count(); i++) { |
622 | 0 | const auto &uid = key.get_uid(i); |
623 | 0 | uint8_t * p = (uint8_t *) mem.memory() + uid_start + (12 * i); |
624 | | /* store absolute uid offset in the output stream */ |
625 | 0 | uint32_t pt = mem.writeb() + dst.writeb; |
626 | 0 | write_uint32(p, pt); |
627 | | /* and uid length */ |
628 | 0 | pt = uid.str.size(); |
629 | 0 | write_uint32(p + 4, pt); |
630 | | /* uid data itself */ |
631 | 0 | if (!pbuf(mem.dst(), uid.str.c_str(), pt)) { |
632 | 0 | return false; |
633 | 0 | } |
634 | 0 | } |
635 | | |
636 | | /* write keyblock and fix the offset/length */ |
637 | 0 | size_t key_start = mem.writeb(); |
638 | 0 | uint32_t pt = key_start; |
639 | 0 | uint8_t *p = (uint8_t *) mem.memory() + 8; |
640 | 0 | write_uint32(p, pt); |
641 | |
|
642 | 0 | key.write(mem.dst()); |
643 | 0 | if (mem.werr()) { |
644 | 0 | return false; |
645 | 0 | } |
646 | | |
647 | 0 | for (auto &sfp : key.subkey_fps()) { |
648 | 0 | auto *subkey = key_store.get_key(sfp); |
649 | 0 | if (!subkey) { |
650 | 0 | return false; |
651 | 0 | } |
652 | 0 | subkey->write(mem.dst()); |
653 | 0 | if (mem.werr()) { |
654 | 0 | return false; |
655 | 0 | } |
656 | 0 | } |
657 | | |
658 | | /* key blob length */ |
659 | 0 | pt = mem.writeb() - key_start; |
660 | 0 | p = (uint8_t *) mem.memory() + 12; |
661 | 0 | write_uint32(p, pt); |
662 | | |
663 | | // fix the length of blob |
664 | 0 | pt = mem.writeb() + 20; |
665 | 0 | p = (uint8_t *) mem.memory(); |
666 | 0 | write_uint32(p, pt); |
667 | | |
668 | | // checksum |
669 | 0 | auto hash = rnp::Hash::create(PGP_HASH_SHA1); |
670 | 0 | hash->add(mem.memory(), mem.writeb()); |
671 | 0 | uint8_t checksum[PGP_SHA1_HASH_SIZE]; |
672 | 0 | assert(hash->size() == sizeof(checksum)); |
673 | 0 | hash->finish(checksum); |
674 | |
|
675 | 0 | if (!(pbuf(mem.dst(), checksum, PGP_SHA1_HASH_SIZE))) { |
676 | 0 | return false; |
677 | 0 | } |
678 | | |
679 | | /* finally write to the output */ |
680 | 0 | dst_write(&dst, mem.memory(), mem.writeb()); |
681 | 0 | return !dst.werr; |
682 | 0 | } |
683 | | |
684 | | bool |
685 | | kbx_write_x509(const KeyStore &key_store, pgp_dest_t &dst) |
686 | 0 | { |
687 | 0 | for (auto &blob : key_store.blobs) { |
688 | 0 | if (blob->type() != KBX_X509_BLOB) { |
689 | 0 | continue; |
690 | 0 | } |
691 | 0 | if (!pbuf(dst, blob->image().data(), blob->length())) { |
692 | 0 | return false; |
693 | 0 | } |
694 | 0 | } |
695 | 0 | return true; |
696 | 0 | } |
697 | | } // namespace |
698 | | |
699 | | bool |
700 | | KeyStore::write_kbx(pgp_dest_t &dst) |
701 | 0 | { |
702 | 0 | try { |
703 | 0 | if (!kbx_write_header(*this, dst)) { |
704 | 0 | RNP_LOG("Can't write KBX header"); |
705 | 0 | return false; |
706 | 0 | } |
707 | | |
708 | 0 | for (auto &key : keys) { |
709 | 0 | if (!key.is_primary()) { |
710 | 0 | continue; |
711 | 0 | } |
712 | 0 | if (!kbx_write_pgp(*this, key, dst)) { |
713 | 0 | RNP_LOG("Can't write PGP blobs for key %p", &key); |
714 | 0 | return false; |
715 | 0 | } |
716 | 0 | } |
717 | | |
718 | 0 | if (!kbx_write_x509(*this, dst)) { |
719 | 0 | RNP_LOG("Can't write X509 blobs"); |
720 | 0 | return false; |
721 | 0 | } |
722 | 0 | return true; |
723 | 0 | } catch (const std::exception &e) { |
724 | | /* LCOV_EXCL_START */ |
725 | 0 | RNP_LOG("Failed to write KBX store: %s", e.what()); |
726 | 0 | return false; |
727 | | /* LCOV_EXCL_END */ |
728 | 0 | } |
729 | 0 | } |
730 | | } // namespace rnp |