1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include <string.h>
8 #include <stdlib.h>
9 #include <fcntl.h>
10 #include "bzlib.h"
11 #include "archivereader.h"
12 #include "errors.h"
13 #ifdef _WIN32
14 #include "updatehelper.h"
15 #endif
16
17 // These are generated at compile time based on the DER file for the channel
18 // being used
19 #ifdef VERIFY_MAR_SIGNATURE
20 #ifdef TEST_UPDATER
21 #include "../xpcshellCert.h"
22 #else
23 #include "onlineupdate/primaryCert.h"
24 #include "onlineupdate/secondaryCert.h"
25 #endif
26 #endif
27
28 #if defined(_WIN32)
29 #define UPDATER_NO_STRING_GLUE_STL
30 #endif
31 #include "nsVersionComparator.h"
32 #undef UPDATER_NO_STRING_GLUE_STL
33
34 #if defined(UNIX)
35 # include <sys/types.h>
36 #elif defined(_WIN32)
37 # include <io.h>
38 #endif
39
40 static int inbuf_size = 262144;
41 static int outbuf_size = 262144;
42 static char *inbuf = nullptr;
43 static char *outbuf = nullptr;
44
45 /**
46 * Performs a verification on the opened MAR file with the passed in
47 * certificate name ID and type ID.
48 *
49 * @param archive The MAR file to verify the signature on.
50 * @param certData The certificate data.
51 * @return OK on success, CERT_VERIFY_ERROR on failure.
52 */
53 template<uint32_t SIZE>
54 int
VerifyLoadedCert(MarFile * archive,const uint8_t (& certData)[SIZE])55 VerifyLoadedCert(MarFile *archive, const uint8_t (&certData)[SIZE])
56 {
57 (void)archive;
58 (void)certData;
59
60 #ifdef VERIFY_MAR_SIGNATURE
61 const uint32_t size = SIZE;
62 const uint8_t* const data = &certData[0];
63 if (mar_verify_signatures(archive, &data, &size, 1))
64 {
65 return CERT_VERIFY_ERROR;
66 }
67 #endif
68
69 return OK;
70 }
71
72 /**
73 * Performs a verification on the opened MAR file. Both the primary and backup
74 * keys stored are stored in the current process and at least the primary key
75 * will be tried. Success will be returned as long as one of the two
76 * signatures verify.
77 *
78 * @return OK on success
79 */
80 int
VerifySignature()81 ArchiveReader::VerifySignature()
82 {
83 if (!mArchive)
84 {
85 return ARCHIVE_NOT_OPEN;
86 }
87
88 #ifndef VERIFY_MAR_SIGNATURE
89 return OK;
90 #else
91 #ifdef TEST_UPDATER
92 int rv = VerifyLoadedCert(mArchive, xpcshellCertData);
93 #else
94 int rv = VerifyLoadedCert(mArchive, primaryCertData);
95 if (rv != OK)
96 {
97 rv = VerifyLoadedCert(mArchive, secondaryCertData);
98 }
99 #endif
100 return rv;
101 #endif
102 }
103
104 /**
105 * Verifies that the MAR file matches the current product, channel, and version
106 *
107 * @param MARChannelID The MAR channel name to use, only updates from MARs
108 * with a matching MAR channel name will succeed.
109 * If an empty string is passed, no check will be done
110 * for the channel name in the product information block.
111 * If a comma separated list of values is passed then
112 * one value must match.
113 * @param appVersion The application version to use, only MARs with an
114 * application version >= to appVersion will be applied.
115 * @return OK on success
116 * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block
117 * could not be read.
118 * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR
119 * channel ID doesn't match the MAR
120 * file's MAR channel ID.
121 * VERSION_DOWNGRADE_ERROR if the application version for
122 * this updater is newer than the
123 * one in the MAR.
124 */
125 int
VerifyProductInformation(const char * MARChannelID,const char * appVersion)126 ArchiveReader::VerifyProductInformation(const char *MARChannelID,
127 const char *appVersion)
128 {
129 if (!mArchive)
130 {
131 return ARCHIVE_NOT_OPEN;
132 }
133
134 ProductInformationBlock productInfoBlock;
135 int rv = mar_read_product_info_block(mArchive,
136 &productInfoBlock);
137 if (rv != OK)
138 {
139 return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR;
140 }
141
142 // Only check the MAR channel name if specified, it should be passed in from
143 // the update-settings.ini file.
144 if (MARChannelID && strlen(MARChannelID))
145 {
146 // Check for at least one match in the comma separated list of values.
147 const char *delimiter = " ,\t";
148 // Make a copy of the string in case a read only memory buffer
149 // was specified. strtok modifies the input buffer.
150 char channelCopy[512] = { 0 };
151 strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1);
152 char *channel = strtok(channelCopy, delimiter);
153 rv = MAR_CHANNEL_MISMATCH_ERROR;
154 while (channel)
155 {
156 if (!strcmp(channel, productInfoBlock.MARChannelID))
157 {
158 rv = OK;
159 break;
160 }
161 channel = strtok(nullptr, delimiter);
162 }
163 }
164
165 if (rv == OK)
166 {
167 /* Compare both versions to ensure we don't have a downgrade
168 -1 if appVersion is older than productInfoBlock.productVersion
169 1 if appVersion is newer than productInfoBlock.productVersion
170 0 if appVersion is the same as productInfoBlock.productVersion
171 This even works with strings like:
172 - 12.0a1 being older than 12.0a2
173 - 12.0a2 being older than 12.0b1
174 - 12.0a1 being older than 12.0
175 - 12.0 being older than 12.1a1 */
176 int versionCompareResult =
177 mozilla::CompareVersions(appVersion, productInfoBlock.productVersion);
178 if (1 == versionCompareResult)
179 {
180 rv = VERSION_DOWNGRADE_ERROR;
181 }
182 }
183
184 free((void *)productInfoBlock.MARChannelID);
185 free((void *)productInfoBlock.productVersion);
186 return rv;
187 }
188
189 int
Open(const NS_tchar * path)190 ArchiveReader::Open(const NS_tchar *path)
191 {
192 if (mArchive)
193 Close();
194
195 if (!inbuf)
196 {
197 inbuf = (char *)malloc(inbuf_size);
198 if (!inbuf)
199 {
200 // Try again with a smaller buffer.
201 inbuf_size = 1024;
202 inbuf = (char *)malloc(inbuf_size);
203 if (!inbuf)
204 return ARCHIVE_READER_MEM_ERROR;
205 }
206 }
207
208 if (!outbuf)
209 {
210 outbuf = (char *)malloc(outbuf_size);
211 if (!outbuf)
212 {
213 // Try again with a smaller buffer.
214 outbuf_size = 1024;
215 outbuf = (char *)malloc(outbuf_size);
216 if (!outbuf)
217 return ARCHIVE_READER_MEM_ERROR;
218 }
219 }
220
221 #ifdef _WIN32
222 mArchive = mar_wopen(path);
223 #else
224 mArchive = mar_open(path);
225 #endif
226 if (!mArchive)
227 return READ_ERROR;
228
229 return OK;
230 }
231
232 void
Close()233 ArchiveReader::Close()
234 {
235 if (mArchive)
236 {
237 mar_close(mArchive);
238 mArchive = nullptr;
239 }
240
241 if (inbuf)
242 {
243 free(inbuf);
244 inbuf = nullptr;
245 }
246
247 if (outbuf)
248 {
249 free(outbuf);
250 outbuf = nullptr;
251 }
252 }
253
254 int
ExtractFile(const char * name,const NS_tchar * dest)255 ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest)
256 {
257 const MarItem *item = mar_find_item(mArchive, name);
258 if (!item)
259 return READ_ERROR;
260
261 #ifdef _WIN32
262 FILE* fp = _wfopen(dest, L"wb+");
263 #else
264 int fd = creat(dest, item->flags);
265 if (fd == -1)
266 return WRITE_ERROR;
267
268 FILE *fp = fdopen(fd, "wb");
269 #endif
270 if (!fp)
271 return WRITE_ERROR;
272
273 int rv = ExtractItemToStream(item, fp);
274
275 fclose(fp);
276 return rv;
277 }
278
279 int
ExtractFileToStream(const char * name,FILE * fp)280 ArchiveReader::ExtractFileToStream(const char *name, FILE *fp)
281 {
282 const MarItem *item = mar_find_item(mArchive, name);
283 if (!item)
284 return READ_ERROR;
285
286 return ExtractItemToStream(item, fp);
287 }
288
289 int
ExtractItemToStream(const MarItem * item,FILE * fp)290 ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp)
291 {
292 /* decompress the data chunk by chunk */
293
294 bz_stream strm;
295 int offset, inlen, ret = OK;
296
297 memset(&strm, 0, sizeof(strm));
298 if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK)
299 return UNEXPECTED_BZIP_ERROR;
300
301 offset = 0;
302 for (;;)
303 {
304 if (!item->length)
305 {
306 ret = UNEXPECTED_MAR_ERROR;
307 break;
308 }
309
310 if (offset < (int) item->length && strm.avail_in == 0)
311 {
312 inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size);
313 if (inlen <= 0)
314 return READ_ERROR;
315 offset += inlen;
316 strm.next_in = inbuf;
317 strm.avail_in = inlen;
318 }
319
320 strm.next_out = outbuf;
321 strm.avail_out = outbuf_size;
322
323 ret = BZ2_bzDecompress(&strm);
324 if (ret != BZ_OK && ret != BZ_STREAM_END)
325 {
326 ret = UNEXPECTED_BZIP_ERROR;
327 break;
328 }
329
330 int outlen = outbuf_size - strm.avail_out;
331 if (outlen)
332 {
333 if (fwrite(outbuf, outlen, 1, fp) != 1)
334 {
335 ret = WRITE_ERROR_EXTRACT;
336 break;
337 }
338 }
339
340 if (ret == BZ_STREAM_END)
341 {
342 ret = OK;
343 break;
344 }
345 }
346
347 BZ2_bzDecompressEnd(&strm);
348 return ret;
349 }
350