| 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 |