GCC Code Coverage Report


Directory: src/
File: src/mpq-signature.c
Date: 2026-10-06 13:47:41
Exec Total Coverage
Lines: 289 320 90.3%
Functions: 14 14 100.0%
Branches: 219 298 73.5%

Line Branch Exec Source
1 /*
2 * mpq-signature.c -- weak MPQ signature hashing and lifecycle.
3 *
4 * Copyright (c) 2003-2026 Maik Broemme <mbroemme@libmpq.org>
5 *
6 * This file is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
10 *
11 * This file is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this file; if not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "mpq-signature.h"
21 #include "mpq-archive.h"
22 #include "mpq-block.h"
23 #include "mpq-crypto.h"
24 #include "mpq-file.h"
25 #include "mpq-md5.h"
26 #include "mpq-reader.h"
27 #include "mpq-rsa.h"
28 #include "mpq-sha1.h"
29 #include "mpq-source.h"
30 #include "mpq-writer.h"
31 #include <string.h>
32
33 /* The signature range has already been bounded by locate before this call. */
34 static int
35 670 overlaps(uint64_t start, uint64_t size, uint64_t signature)
36 {
37
3/4
✓ Branch 0 taken 544 times.
✓ Branch 1 taken 126 times.
✓ Branch 2 taken 544 times.
✗ Branch 3 not taken.
1214 return start < signature + LIBMPQ_SIGNATURE_SIZE &&
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 544 times.
544 (start > signature || size > signature - start);
39 }
40
41 /*
42 * Inspect the physical hash/block entry, including entries excluded from the
43 * public reader map. An invalid internal file must not look like absence.
44 */
45 static int32_t
46 88 locate(mpq_archive_s *a, uint64_t *offset, uint64_t *extent, uint8_t payload[LIBMPQ_SIGNATURE_SIZE])
47 {
48 uint32_t h1;
49 uint32_t h2;
50 uint32_t h3;
51 uint32_t i;
52 88 uint32_t index = UINT32_MAX;
53 uint64_t size;
54 uint64_t pos;
55 uint64_t table;
56 uint64_t table_size;
57 int32_t result;
58 88 libmpq__file_hash(LIBMPQ_SIGNATURE_NAME, &h1, &h2, &h3);
59
2/2
✓ Branch 0 taken 17347 times.
✓ Branch 1 taken 88 times.
17435 for (i = 0; i < a->mpq_header.hash_table_count; ++i) {
60 17347 const mpq_hash_s *entry = &a->mpq_hash[i];
61
4/4
✓ Branch 0 taken 761 times.
✓ Branch 1 taken 16586 times.
✓ Branch 2 taken 73 times.
✓ Branch 3 taken 688 times.
17347 if (entry->block_table_index >= LIBMPQ_HASH_DELETED || entry->hash_a != h2 ||
62
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 73 times.
73 entry->hash_b != h3)
63 17274 continue;
64
3/6
✓ Branch 0 taken 73 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 73 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 73 times.
✗ Branch 5 not taken.
73 if (index != UINT32_MAX || entry->locale != 0 || entry->platform != 0 ||
65
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 73 times.
73 entry->block_table_index >= a->mpq_header.block_table_count)
66 ✗ return LIBMPQ_ERROR_FORMAT;
67 73 index = entry->block_table_index;
68 }
69
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 73 times.
88 if (index == UINT32_MAX)
70 15 return LIBMPQ_ERROR_EXIST;
71 73 result = libmpq__archive_signature_extent(a, &size);
72
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 68 times.
73 if (result != 0)
73 5 return result;
74
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 4 times.
68 if (a->mpq_block[index].unpacked_size != LIBMPQ_SIGNATURE_SIZE ||
75
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 1 times.
64 a->mpq_block[index].packed_size != LIBMPQ_SIGNATURE_SIZE ||
76
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 60 times.
63 (a->mpq_block[index].flags & ~LIBMPQ_FLAG_SINGLE) != LIBMPQ_FLAG_EXISTS)
77 8 return LIBMPQ_ERROR_FORMAT;
78 60 pos = a->mpq_block[index].offset | ((uint64_t)a->mpq_block_ex[index].offset_high << 32);
79
2/4
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
60 if (a->archive_offset < 0 || size < a->mpq_header.header_size ||
80
3/6
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
60 pos < a->mpq_header.header_size || pos > size || size - pos < LIBMPQ_SIGNATURE_SIZE ||
81
1/2
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
60 (uint64_t)a->archive_offset > a->file_size ||
82
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
60 size > a->file_size - (uint64_t)a->archive_offset)
83 ✗ return LIBMPQ_ERROR_FORMAT;
84 60 table =
85 60 a->mpq_header.hash_table_offset | ((uint64_t)a->mpq_header_ex.hash_table_offset_high << 32);
86 60 table_size = (uint64_t)a->mpq_header.hash_table_count * LIBMPQ_HASH_ENTRY_WIRE_SIZE;
87
3/6
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 60 times.
60 if (table > size || table_size > size - table || overlaps(table, table_size, pos))
88 ✗ return LIBMPQ_ERROR_FORMAT;
89 60 table = a->mpq_header.block_table_offset |
90 60 ((uint64_t)a->mpq_header_ex.block_table_offset_high << 32);
91 60 table_size = (uint64_t)a->mpq_header.block_table_count * LIBMPQ_BLOCK_ENTRY_WIRE_SIZE;
92
3/6
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 60 times.
60 if (table > size || table_size > size - table || overlaps(table, table_size, pos))
93 ✗ return LIBMPQ_ERROR_FORMAT;
94 60 table = a->mpq_header_ex.extended_offset;
95 60 table_size = (uint64_t)a->mpq_header.block_table_count * LIBMPQ_BLOCK_EX_ENTRY_WIRE_SIZE;
96
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 44 times.
60 if (table != 0 &&
97
3/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
16 (table > size || table_size > size - table || overlaps(table, table_size, pos)))
98 ✗ return LIBMPQ_ERROR_FORMAT;
99
2/2
✓ Branch 0 taken 7282 times.
✓ Branch 1 taken 60 times.
7342 for (i = 0; i < a->mpq_header.block_table_count; ++i) {
100 7282 uint64_t other = a->mpq_block[i].offset | ((uint64_t)a->mpq_block_ex[i].offset_high << 32);
101
4/4
✓ Branch 0 taken 598 times.
✓ Branch 1 taken 6684 times.
✓ Branch 2 taken 594 times.
✓ Branch 3 taken 4 times.
7282 if ((a->mpq_block[i].flags & LIBMPQ_FLAG_EXISTS) && a->mpq_block[i].packed_size != 0) {
102
4/6
✓ Branch 0 taken 594 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 594 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 534 times.
✓ Branch 5 taken 60 times.
594 if (other > size || a->mpq_block[i].packed_size > size - other ||
103
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 534 times.
534 (i != index && overlaps(other, a->mpq_block[i].packed_size, pos)))
104 ✗ return LIBMPQ_ERROR_FORMAT;
105 }
106 }
107 60 result = libmpq__source_read_at(
108 60 a->source, (uint64_t)a->archive_offset + pos, payload, LIBMPQ_SIGNATURE_SIZE
109 );
110
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 59 times.
60 if (result != 0)
111 1 return result;
112
2/2
✓ Branch 0 taken 465 times.
✓ Branch 1 taken 58 times.
523 for (i = 0; i < LIBMPQ_SIGNATURE_PREFIX_SIZE; ++i)
113
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 464 times.
465 if (payload[i] != 0)
114 1 return LIBMPQ_ERROR_FORMAT;
115 58 *offset = pos;
116 58 *extent = size;
117 58 return 0;
118 }
119
120 /*
121 * Hash only the logical archive, zeroing every intersection with the internal
122 * signature payload. The same read-at implementation handles finalized writers.
123 */
124 static int32_t
125 33 digest_archive(
126 mpq_source_s *source, uint64_t start, uint64_t size, uint64_t excluded,
127 uint8_t digest[LIBMPQ_MD5_SIZE]
128 )
129 {
130 mpq_md5_s context;
131 uint8_t buffer[16384];
132 33 uint64_t pos = 0;
133
3/6
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 33 times.
33 if (start > UINT64_MAX - size || excluded > size || size - excluded < LIBMPQ_SIGNATURE_SIZE)
134 ✗ return LIBMPQ_ERROR_FORMAT;
135 33 libmpq__md5_init(&context);
136
2/2
✓ Branch 0 taken 71 times.
✓ Branch 1 taken 32 times.
103 while (pos < size) {
137 71 size_t count = size - pos < sizeof(buffer) ? (size_t)(size - pos) : sizeof(buffer);
138 71 uint64_t begin = pos > excluded ? pos : excluded;
139 71 uint64_t end = pos + count < excluded + LIBMPQ_SIGNATURE_SIZE
140 ? pos + count
141 : excluded + LIBMPQ_SIGNATURE_SIZE;
142 71 int32_t result = libmpq__source_read_at(source, start + pos, buffer, count);
143
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 70 times.
71 if (result != 0)
144 1 return result;
145
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 30 times.
70 if (begin < end)
146 40 memset(buffer + (size_t)(begin - pos), 0, (size_t)(end - begin));
147 70 libmpq__md5_update(&context, buffer, count);
148 70 pos += count;
149 }
150 32 libmpq__md5_final(&context, digest);
151 32 return 0;
152 }
153
154 /*
155 * Locate a structurally complete external NGIS trailer. Trailing data is
156 * accepted for compatibility with Blizzard-format archives.
157 */
158 static int32_t
159 77 strong_locate(mpq_archive_s *a, uint64_t *extent, uint8_t signature[LIBMPQ_STRONG_SIGNATURE_SIZE])
160 {
161 static const uint8_t marker[LIBMPQ_STRONG_SIGNATURE_MARKER_SIZE] = { 'N', 'G', 'I', 'S' };
162 uint8_t trailer[LIBMPQ_STRONG_TRAILER_SIZE];
163 uint64_t offset;
164 int32_t result;
165
166 /*
167 * MPQE encrypts its complete transport source and has no defined external
168 * strong-trailer representation. Do not interpret ciphertext as NGIS.
169 */
170
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 69 times.
77 if (libmpq__source_is_mpqe(a->source))
171 8 return LIBMPQ_ERROR_EXIST;
172 69 result = libmpq__archive_signature_extent(a, extent);
173
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69 times.
69 if (result != 0)
174 ✗ return result;
175
2/4
✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 69 times.
69 if (a->archive_offset < 0 || (uint64_t)a->archive_offset > UINT64_MAX - *extent)
176 ✗ return LIBMPQ_ERROR_FORMAT;
177 69 offset = (uint64_t)a->archive_offset + *extent;
178
3/4
✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 49 times.
69 if (offset > a->file_size || LIBMPQ_STRONG_TRAILER_SIZE > a->file_size - offset)
179 20 return LIBMPQ_ERROR_EXIST;
180 49 result = libmpq__source_read_at(a->source, offset, trailer, sizeof(trailer));
181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (result != 0)
182 ✗ return result;
183
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 46 times.
49 if (memcmp(trailer, marker, sizeof(marker)) != 0)
184 3 return LIBMPQ_ERROR_EXIST;
185 46 memcpy(signature, trailer + sizeof(marker), LIBMPQ_STRONG_SIGNATURE_SIZE);
186 46 return 0;
187 }
188
189 /* HM3W is a container-specific signed-range rule. */
190 static int32_t
191 51 signature_hash_range(mpq_archive_s *a, uint64_t extent, uint64_t *start, uint64_t *size)
192 {
193 static const uint8_t hm3w[4] = { 'H', 'M', '3', 'W' };
194 uint8_t prefix[sizeof(hm3w)];
195 uint64_t end;
196 int32_t result;
197
2/4
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 51 times.
51 if (a->archive_offset < 0 || (uint64_t)a->archive_offset > UINT64_MAX - extent)
198 ✗ return LIBMPQ_ERROR_FORMAT;
199 51 end = (uint64_t)a->archive_offset + extent;
200 51 *start = (uint64_t)a->archive_offset;
201
3/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
51 if (a->archive_offset > 0 && a->file_size >= sizeof(prefix)) {
202 12 result = libmpq__source_read_at(a->source, 0, prefix, sizeof(prefix));
203
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (result != 0)
204 ✗ return result;
205
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
12 if (memcmp(prefix, hm3w, sizeof(prefix)) == 0)
206 9 *start = 0;
207 }
208
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
51 if (*start > end)
209 ✗ return LIBMPQ_ERROR_FORMAT;
210 51 *size = end - *start;
211 51 return 0;
212 }
213
214 static int32_t
215 25 strong_digest_base(mpq_archive_s *a, uint64_t start, uint64_t size, mpq_sha1_s *context)
216 {
217 uint8_t buffer[16384];
218 25 uint64_t pos = 0;
219 int32_t result;
220 25 libmpq__sha1_init(context);
221
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 25 times.
76 while (pos < size) {
222 51 size_t count = size - pos < sizeof(buffer) ? (size_t)(size - pos) : sizeof(buffer);
223 51 result = libmpq__source_read_at(a->source, start + pos, buffer, count);
224
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
51 if (result != 0)
225 ✗ return result;
226 51 libmpq__sha1_update(context, buffer, count);
227 51 pos += count;
228 }
229 25 return 0;
230 }
231
232 static int32_t
233 49 strong_digest_variant(
234 mpq_archive_s *a, const mpq_sha1_s *base, unsigned int variant, uint8_t digest[LIBMPQ_SHA1_SIZE]
235 )
236 {
237 static const uint8_t archive_suffix[] = "ARCHIVE";
238 49 mpq_sha1_s context = *base;
239
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 35 times.
49 if (variant == 1u) {
240 14 const char *name = a->filename;
241 14 const char *base = name;
242 size_t i;
243
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 11 times.
14 if (name == NULL)
244 3 return LIBMPQ_ERROR_EXIST;
245
2/2
✓ Branch 0 taken 379 times.
✓ Branch 1 taken 11 times.
390 for (i = 0; name[i] != '\0'; ++i)
246
4/4
✓ Branch 0 taken 361 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 359 times.
379 if (name[i] == '/' || name[i] == '\\')
247 20 base = name + i + 1;
248
2/2
✓ Branch 0 taken 263 times.
✓ Branch 1 taken 11 times.
274 for (i = 0; base[i] != '\0'; ++i) {
249 263 uint8_t character = (uint8_t)base[i];
250
3/4
✓ Branch 0 taken 196 times.
✓ Branch 1 taken 67 times.
✓ Branch 2 taken 196 times.
✗ Branch 3 not taken.
263 if (character >= 'a' && character <= 'z')
251 196 character = (uint8_t)(character - ('a' - 'A'));
252 263 libmpq__sha1_update(&context, &character, 1);
253 }
254
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 25 times.
35 } else if (variant == 2u) {
255 10 libmpq__sha1_update(&context, archive_suffix, sizeof(archive_suffix) - 1);
256 }
257 46 libmpq__sha1_final(&context, digest);
258 46 return 0;
259 }
260
261 static void
262 51 strong_encoded(
263 const uint8_t digest[LIBMPQ_SHA1_SIZE], uint8_t encoded[LIBMPQ_STRONG_SIGNATURE_SIZE]
264 )
265 {
266 size_t i;
267 51 encoded[0] = 0x0b;
268 51 memset(encoded + 1, 0xbb, LIBMPQ_STRONG_SIGNATURE_SIZE - LIBMPQ_SHA1_SIZE - 1);
269
2/2
✓ Branch 0 taken 1020 times.
✓ Branch 1 taken 51 times.
1071 for (i = 0; i < LIBMPQ_SHA1_SIZE; ++i)
270 1020 encoded[LIBMPQ_STRONG_SIGNATURE_SIZE - LIBMPQ_SHA1_SIZE + i] =
271 1020 digest[LIBMPQ_SHA1_SIZE - 1 - i];
272 51 }
273
274 static int32_t
275 34 strong_verify(mpq_archive_s *a, const uint8_t *key, size_t key_size, uint32_t *mismatches)
276 {
277 uint8_t signature[LIBMPQ_STRONG_SIGNATURE_SIZE];
278 uint8_t input[LIBMPQ_STRONG_SIGNATURE_SIZE];
279 uint8_t actual[LIBMPQ_STRONG_SIGNATURE_SIZE];
280 uint8_t expected[LIBMPQ_STRONG_SIGNATURE_SIZE];
281 uint8_t digest[LIBMPQ_SHA1_SIZE];
282 mpq_sha1_s base;
283 uint64_t extent;
284 uint64_t start;
285 uint64_t size;
286 size_t i;
287 unsigned int variant;
288 int32_t result;
289
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 30 times.
34 if (libmpq__rsa_strong_key_validate(key, key_size) != 0)
290 4 return LIBMPQ_ERROR_FORMAT;
291 30 result = strong_locate(a, &extent, signature);
292
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 26 times.
30 if (result != 0)
293 4 return result;
294 26 result = signature_hash_range(a, extent, &start, &size);
295
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (result != 0)
296 ✗ return result;
297
2/2
✓ Branch 0 taken 6656 times.
✓ Branch 1 taken 26 times.
6682 for (i = 0; i < sizeof(input); ++i)
298 6656 input[i] = signature[sizeof(input) - 1 - i];
299
300 /*
301 * A trailer value outside the RSA representative domain is a failed
302 * signature, not malformed MPQ storage or an invalid caller key.
303 */
304
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 25 times.
26 if (memcmp(input, key, LIBMPQ_RSA_STRONG_SIZE) >= 0) {
305 1 *mismatches |= LIBMPQ_SIGNATURE_STRONG;
306 1 return 0;
307 }
308 25 result = libmpq__rsa_strong_public_operation(key, input, actual);
309
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 if (result != 0)
310 ✗ return result;
311 25 result = strong_digest_base(a, start, size, &base);
312
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 if (result != 0)
313 ✗ return result;
314
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 7 times.
56 for (variant = 0; variant < 3; ++variant) {
315 49 result = strong_digest_variant(a, &base, variant, digest);
316
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
49 if (result == LIBMPQ_ERROR_EXIST && variant == 1u)
317 3 continue;
318
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (result != 0)
319 ✗ return result;
320 46 strong_encoded(digest, expected);
321
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 28 times.
46 if (memcmp(actual, expected, sizeof(actual)) == 0)
322 18 return 0;
323 }
324 7 *mismatches |= LIBMPQ_SIGNATURE_STRONG;
325 7 return 0;
326 }
327
328 int32_t
329 59 libmpq__signature_detect(mpq_archive_s *a, uint32_t *signatures)
330 {
331 uint8_t payload[LIBMPQ_SIGNATURE_SIZE];
332 uint8_t strong[LIBMPQ_STRONG_SIGNATURE_SIZE];
333 uint64_t offset;
334 uint64_t extent;
335 int32_t result;
336
1/2
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
59 if (signatures != NULL)
337 59 *signatures = 0;
338
3/4
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 58 times.
59 if (a == NULL || signatures == NULL)
339 1 return LIBMPQ_ERROR_EXIST;
340
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 58 times.
58 if (a->write_mode)
341 ✗ return LIBMPQ_ERROR_NOT_INITIALIZED;
342 58 result = locate(a, &offset, &extent, payload);
343
4/4
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 33 times.
58 if (result != LIBMPQ_ERROR_EXIST && result != 0)
344 11 return result;
345
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 14 times.
47 if (result == 0)
346 33 *signatures = LIBMPQ_SIGNATURE_WEAK;
347 47 result = strong_locate(a, &extent, strong);
348
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 20 times.
47 if (result == LIBMPQ_ERROR_EXIST)
349 27 return 0;
350
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if (result == 0)
351 20 *signatures |= LIBMPQ_SIGNATURE_STRONG;
352 20 return result;
353 }
354
355 int32_t
356 67 libmpq__signature_verify(
357 mpq_archive_s *a, uint32_t flags, const uint8_t *key, size_t key_size, uint32_t *mismatches
358 )
359 {
360 uint8_t payload[LIBMPQ_SIGNATURE_SIZE];
361 uint8_t digest[LIBMPQ_MD5_SIZE];
362 uint8_t expected[LIBMPQ_RSA_SIZE];
363 uint8_t input[LIBMPQ_RSA_SIZE];
364 uint8_t actual[LIBMPQ_RSA_SIZE];
365 uint64_t offset;
366 uint64_t extent;
367 uint64_t start;
368 uint64_t size;
369 uint64_t excluded;
370 size_t i;
371 int32_t result;
372
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 if (mismatches != NULL)
373 67 *mismatches = 0;
374
3/4
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 66 times.
67 if (a == NULL || mismatches == NULL)
375 1 return LIBMPQ_ERROR_EXIST;
376
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66 times.
66 if (a->write_mode)
377 ✗ return LIBMPQ_ERROR_NOT_INITIALIZED;
378
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 32 times.
66 if (flags == LIBMPQ_SIGNATURE_STRONG)
379 34 return strong_verify(a, key, key_size, mismatches);
380
4/4
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 30 times.
32 if (flags != LIBMPQ_SIGNATURE_WEAK || libmpq__rsa_weak_key_validate(key, key_size) != 0)
381 2 return LIBMPQ_ERROR_FORMAT;
382 30 result = locate(a, &offset, &extent, payload);
383
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 25 times.
30 if (result != 0)
384 5 return result;
385 25 result = signature_hash_range(a, extent, &start, &size);
386
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 if (result != 0)
387 ✗ return result;
388
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 if ((uint64_t)a->archive_offset > UINT64_MAX - offset ||
389
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 (uint64_t)a->archive_offset + offset < start)
390 ✗ return LIBMPQ_ERROR_FORMAT;
391 25 excluded = (uint64_t)a->archive_offset + offset - start;
392 25 result = digest_archive(a->source, start, size, excluded, digest);
393
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 24 times.
25 if (result != 0)
394 1 return result;
395 24 libmpq__rsa_md5_encode(digest, expected);
396
2/2
✓ Branch 0 taken 1536 times.
✓ Branch 1 taken 24 times.
1560 for (i = 0; i < LIBMPQ_RSA_SIZE; ++i)
397 1536 input[i] = payload[(LIBMPQ_SIGNATURE_SIZE - 1) - i];
398
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 if (libmpq__rsa_weak_operation(key, input, actual) != 0 ||
399
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 19 times.
24 memcmp(expected, actual, LIBMPQ_RSA_SIZE) != 0)
400 5 *mismatches = LIBMPQ_SIGNATURE_WEAK;
401 24 return 0;
402 }
403
404 /*
405 * Reserve the zero payload immediately. Duplicate names are rejected by the
406 * writer, and existing listfile/attributes reservations remain in force.
407 */
408 int32_t
409 46 libmpq__signature_configure(mpq_archive_s *a, uint32_t type, const uint8_t *key, size_t key_size)
410 {
411 46 uint8_t zero[LIBMPQ_SIGNATURE_SIZE] = { 0 };
412 46 mpq_file_options_s options = { 0, 0, 0, 0, 0 };
413 uint32_t index;
414 uint32_t i;
415 uint32_t h1;
416 uint32_t h2;
417 uint32_t h3;
418 int32_t result;
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (a == NULL)
420 ✗ return LIBMPQ_ERROR_EXIST;
421
4/6
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 45 times.
46 if (!a->write_mode || a->write_finalized || a->write_current)
422 1 return LIBMPQ_ERROR_NOT_INITIALIZED;
423
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 24 times.
45 if (type == LIBMPQ_SIGNATURE_STRONG) {
424
6/6
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 5 times.
36 if (a->write_mpqe || a->write_strong_signature ||
425 15 libmpq__rsa_strong_key_validate(key, key_size) != 0)
426 16 return LIBMPQ_ERROR_FORMAT;
427 5 memcpy(a->write_strong_signature_key, key, LIBMPQ_RSA_STRONG_KEY_SIZE);
428 5 a->write_strong_signature = 1;
429 5 return 0;
430 }
431
6/6
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 8 times.
38 if (type != LIBMPQ_SIGNATURE_WEAK || a->write_signature ||
432 14 libmpq__rsa_weak_key_validate(key, key_size) != 0)
433 16 return LIBMPQ_ERROR_FORMAT;
434 8 index = a->write_next_block;
435 8 libmpq__file_hash(LIBMPQ_SIGNATURE_NAME, &h1, &h2, &h3);
436
2/2
✓ Branch 0 taken 8256 times.
✓ Branch 1 taken 8 times.
8264 for (i = 0; i < a->write_hash_capacity; ++i) {
437
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8255 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
8256 if (a->mpq_hash[i].block_table_index < a->write_capacity && a->mpq_hash[i].hash_a == h2 &&
438 ✗ a->mpq_hash[i].hash_b == h3)
439 ✗ return LIBMPQ_ERROR_FORMAT;
440 }
441 8 result = libmpq__writer_file_add(a, LIBMPQ_SIGNATURE_NAME, zero, sizeof(zero), &options);
442
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (result != 0)
443 ✗ return result;
444
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
8 if (a->write_attributes != NULL)
445 5 memset(&a->write_attributes[index], 0, sizeof(*a->write_attributes));
446 8 a->write_signature_offset = a->mpq_block[index].offset;
447 8 memcpy(a->write_signature_key, key, LIBMPQ_RSA_KEY_SIZE);
448 8 a->write_signature = 1;
449 8 return 0;
450 }
451
452 static int32_t
453 615 strong_finish(mpq_archive_s *a, uint64_t size)
454 {
455 static const uint8_t marker[LIBMPQ_STRONG_SIGNATURE_MARKER_SIZE] = { 'N', 'G', 'I', 'S' };
456 615 mpq_source_s source = { 0 };
457 mpq_sha1_s context;
458 uint8_t buffer[16384];
459 uint8_t digest[LIBMPQ_SHA1_SIZE];
460 uint8_t encoded[LIBMPQ_STRONG_SIGNATURE_SIZE];
461 uint8_t signature[LIBMPQ_STRONG_SIGNATURE_SIZE];
462 uint8_t reversed[LIBMPQ_STRONG_SIGNATURE_SIZE];
463 615 uint64_t pos = 0;
464 size_t i;
465 615 int32_t result = 0;
466
467
2/2
✓ Branch 0 taken 610 times.
✓ Branch 1 taken 5 times.
615 if (!a->write_strong_signature)
468 610 return 0;
469 5 result = libmpq__source_borrow_file(&source, a->fp, size);
470 5 libmpq__sha1_init(&context);
471
3/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 5 times.
16 while (result == 0 && pos < size) {
472 11 size_t count = size - pos < sizeof(buffer) ? (size_t)(size - pos) : sizeof(buffer);
473 11 result = libmpq__source_read_at(&source, pos, buffer, count);
474
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (result != 0)
475 ✗ break;
476 11 libmpq__sha1_update(&context, buffer, count);
477 11 pos += count;
478 }
479
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (result == 0) {
480 5 libmpq__sha1_final(&context, digest);
481 5 strong_encoded(digest, encoded);
482 result =
483 5 libmpq__rsa_strong_private_operation(a->write_strong_signature_key, encoded, signature);
484 }
485
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (result == 0) {
486
2/2
✓ Branch 0 taken 1280 times.
✓ Branch 1 taken 5 times.
1285 for (i = 0; i < sizeof(reversed); ++i)
487 1280 reversed[i] = signature[sizeof(reversed) - 1 - i];
488
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if (libmpq__file_seek(a->fp, (libmpq__off_t)size, SEEK_SET) < 0)
489 ✗ result = LIBMPQ_ERROR_SEEK;
490
2/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
10 else if (fwrite(marker, 1, sizeof(marker), a->fp) != sizeof(marker) ||
491
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
10 fwrite(reversed, 1, sizeof(reversed), a->fp) != sizeof(reversed) ||
492 5 fflush(a->fp) != 0)
493 ✗ result = LIBMPQ_ERROR_WRITE;
494 }
495 5 libmpq__rsa_clear(a->write_strong_signature_key, sizeof(a->write_strong_signature_key));
496 5 libmpq__rsa_clear(signature, sizeof(signature));
497 5 libmpq__rsa_clear(reversed, sizeof(reversed));
498 5 libmpq__source_discard(&source);
499 5 return result;
500 }
501
502 int32_t
503 615 libmpq__signature_finish(mpq_archive_s *a, uint64_t size)
504 {
505 615 mpq_source_s source = { 0 };
506 uint8_t digest[LIBMPQ_MD5_SIZE];
507 uint8_t encoded[LIBMPQ_RSA_SIZE];
508 uint8_t signature[LIBMPQ_RSA_SIZE];
509 uint8_t reversed[LIBMPQ_RSA_SIZE];
510 size_t i;
511 615 int32_t result = 0;
512
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 607 times.
615 if (a->write_signature) {
513 8 result = libmpq__source_borrow_file(&source, a->fp, size);
514
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (result == 0)
515 8 result = digest_archive(&source, 0, size, a->write_signature_offset, digest);
516
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (result == 0) {
517 8 libmpq__rsa_md5_encode(digest, encoded);
518 8 result = libmpq__rsa_weak_operation(a->write_signature_key, encoded, signature);
519 }
520
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (result == 0) {
521
2/2
✓ Branch 0 taken 512 times.
✓ Branch 1 taken 8 times.
520 for (i = 0; i < LIBMPQ_RSA_SIZE; ++i)
522 512 reversed[i] = signature[(LIBMPQ_RSA_SIZE - 1) - i];
523
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (libmpq__file_seek(
524 8 a->fp, (libmpq__off_t)a->write_signature_offset + LIBMPQ_SIGNATURE_PREFIX_SIZE,
525 SEEK_SET
526 ) < 0)
527 ✗ result = LIBMPQ_ERROR_SEEK;
528
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
16 else if (fwrite(reversed, 1, LIBMPQ_RSA_SIZE, a->fp) != LIBMPQ_RSA_SIZE ||
529 8 fflush(a->fp) != 0)
530 ✗ result = LIBMPQ_ERROR_WRITE;
531 }
532 }
533 615 libmpq__rsa_clear(a->write_signature_key, sizeof(a->write_signature_key));
534 615 libmpq__rsa_clear(signature, sizeof(signature));
535 615 libmpq__rsa_clear(reversed, sizeof(reversed));
536 615 libmpq__source_discard(&source);
537
1/2
✓ Branch 0 taken 615 times.
✗ Branch 1 not taken.
615 if (result == 0)
538 615 result = strong_finish(a, size);
539 615 return result;
540 }
541