VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/checksum/manifest.cpp@ 45352

Last change on this file since 45352 was 45227, checked in by vboxsync, 12 years ago

Main: OVF 2.0 support. Part 1 - SHA256 digest is supported.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.7 KB
Line 
1/* $Id: manifest.cpp 45227 2013-03-28 12:22:11Z vboxsync $ */
2/** @file
3 * IPRT - Manifest file handling.
4 */
5
6/*
7 * Copyright (C) 2009-2012 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#include "internal/iprt.h"
32#include <iprt/manifest.h>
33
34#include <iprt/err.h>
35#include <iprt/file.h>
36#include <iprt/mem.h>
37#include <iprt/path.h>
38#include <iprt/sha.h>
39#include <iprt/stream.h>
40#include <iprt/string.h>
41
42
43/*******************************************************************************
44* Structures and Typedefs *
45*******************************************************************************/
46/**
47 * Internal per file structure used by RTManifestVerify
48 */
49typedef struct RTMANIFESTFILEENTRY
50{
51 char *pszManifestFile;
52 char *pszManifestDigest;
53 PRTMANIFESTTEST pTestPattern;
54} RTMANIFESTFILEENTRY;
55typedef RTMANIFESTFILEENTRY* PRTMANIFESTFILEENTRY;
56
57/**
58 * Internal structure used for the progress callback
59 */
60typedef struct RTMANIFESTCALLBACKDATA
61{
62 PFNRTPROGRESS pfnProgressCallback;
63 void *pvUser;
64 size_t cMaxFiles;
65 size_t cCurrentFile;
66} RTMANIFESTCALLBACKDATA;
67typedef RTMANIFESTCALLBACKDATA* PRTMANIFESTCALLBACKDATA;
68
69/*******************************************************************************
70* Private functions
71*******************************************************************************/
72
73DECLINLINE(char *) rtManifestPosOfCharInBuf(char const *pv, size_t cb, char c)
74{
75 char *pb = (char *)pv;
76 for (; cb; --cb, ++pb)
77 if (RT_UNLIKELY(*pb == c))
78 return pb;
79 return NULL;
80}
81
82DECLINLINE(size_t) rtManifestIndexOfCharInBuf(char const *pv, size_t cb, char c)
83{
84 char const *pb = (char const *)pv;
85 for (size_t i=0; i < cb; ++i, ++pb)
86 if (RT_UNLIKELY(*pb == c))
87 return i;
88 return cb;
89}
90
91int rtSHAProgressCallback(unsigned uPercent, void *pvUser)
92{
93 PRTMANIFESTCALLBACKDATA pData = (PRTMANIFESTCALLBACKDATA)pvUser;
94 return pData->pfnProgressCallback((unsigned)( (uPercent + (float)pData->cCurrentFile * 100.0)
95 / (float)pData->cMaxFiles),
96 pData->pvUser);
97}
98
99/*******************************************************************************
100* Public functions
101*******************************************************************************/
102
103RTR3DECL(int) RTManifestVerify(const char *pszManifestFile, PRTMANIFESTTEST paTests, size_t cTests, size_t *piFailed)
104{
105 /* Validate input */
106 AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
107
108 /* Open the manifest file */
109 RTFILE file;
110 int rc = RTFileOpen(&file, pszManifestFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE);
111 if (RT_FAILURE(rc))
112 return rc;
113
114 void *pvBuf = 0;
115 do
116 {
117 uint64_t cbSize;
118 rc = RTFileGetSize(file, &cbSize);
119 if (RT_FAILURE(rc))
120 break;
121
122 /* Cast down for the case size_t < uint64_t. This isn't really correct,
123 but we consider manifest files bigger than size_t as not supported
124 by now. */
125 size_t cbToRead = (size_t)cbSize;
126 pvBuf = RTMemAlloc(cbToRead);
127 if (!pvBuf)
128 {
129 rc = VERR_NO_MEMORY;
130 break;
131 }
132
133 size_t cbRead = 0;
134 rc = RTFileRead(file, pvBuf, cbToRead, &cbRead);
135 if (RT_FAILURE(rc))
136 break;
137
138 rc = RTManifestVerifyFilesBuf(pvBuf, cbRead, paTests, cTests, piFailed);
139 }while (0);
140
141 /* Cleanup */
142 if (pvBuf)
143 RTMemFree(pvBuf);
144
145 RTFileClose(file);
146
147 return rc;
148}
149
150RTR3DECL(int) RTManifestVerifyFiles(const char *pszManifestFile, const char * const *papszFiles, size_t cFiles, size_t *piFailed,
151 PFNRTPROGRESS pfnProgressCallback, void *pvUser)
152{
153 /* Validate input */
154 AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
155 AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
156 AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_POINTER);
157
158 int rc = VINF_SUCCESS;
159
160 /* Create our compare list */
161 PRTMANIFESTTEST paFiles = (PRTMANIFESTTEST)RTMemTmpAllocZ(sizeof(RTMANIFESTTEST) * cFiles);
162 if (!paFiles)
163 return VERR_NO_MEMORY;
164
165 RTMANIFESTCALLBACKDATA callback = { pfnProgressCallback, pvUser, cFiles, 0 };
166 /* Fill our compare list */
167 for (size_t i = 0; i < cFiles; ++i)
168 {
169 char *pszDigest;
170 if (pfnProgressCallback)
171 {
172 callback.cCurrentFile = i;
173 rc = RTSha1DigestFromFile(papszFiles[i], &pszDigest, rtSHAProgressCallback, &callback);
174 }
175 else
176 rc = RTSha1DigestFromFile(papszFiles[i], &pszDigest, NULL, NULL);
177 if (RT_FAILURE(rc))
178 break;
179 paFiles[i].pszTestFile = (char*)papszFiles[i];
180 paFiles[i].pszTestDigest = pszDigest;
181 }
182
183 /* Do the verification */
184 if (RT_SUCCESS(rc))
185 rc = RTManifestVerify(pszManifestFile, paFiles, cFiles, piFailed);
186
187 /* Cleanup */
188 for (size_t i = 0; i < cFiles; ++i)
189 {
190 if (paFiles[i].pszTestDigest)
191 RTStrFree((char*)paFiles[i].pszTestDigest);
192 }
193 RTMemTmpFree(paFiles);
194
195 return rc;
196}
197
198RTR3DECL(int) RTManifestWriteFiles(const char *pszManifestFile, RTDIGESTTYPE enmDigestType,
199 const char * const *papszFiles, size_t cFiles,
200 PFNRTPROGRESS pfnProgressCallback, void *pvUser)
201{
202 /* Validate input */
203 AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
204 AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
205 AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_POINTER);
206
207 RTFILE file;
208 int rc = RTFileOpen(&file, pszManifestFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL);
209 if (RT_FAILURE(rc))
210 return rc;
211
212 PRTMANIFESTTEST paFiles = 0;
213 void *pvBuf = 0;
214 do
215 {
216 paFiles = (PRTMANIFESTTEST)RTMemAllocZ(sizeof(RTMANIFESTTEST) * cFiles);
217 if (!paFiles)
218 {
219 rc = VERR_NO_MEMORY;
220 break;
221 }
222
223 RTMANIFESTCALLBACKDATA callback = { pfnProgressCallback, pvUser, cFiles, 0 };
224 for (size_t i = 0; i < cFiles; ++i)
225 {
226 paFiles[i].pszTestFile = papszFiles[i];
227 /* Calculate the SHA1 digest of every file */
228 if (pfnProgressCallback)
229 {
230 callback.cCurrentFile = i;
231 rc = RTSha1DigestFromFile(paFiles[i].pszTestFile, (char**)&paFiles[i].pszTestDigest, rtSHAProgressCallback, &callback);
232 }
233 else
234 rc = RTSha1DigestFromFile(paFiles[i].pszTestFile, (char**)&paFiles[i].pszTestDigest, NULL, NULL);
235 if (RT_FAILURE(rc))
236 break;
237 }
238
239 if (RT_SUCCESS(rc))
240 {
241 size_t cbSize = 0;
242 rc = RTManifestWriteFilesBuf(&pvBuf, &cbSize, enmDigestType, paFiles, cFiles);
243 if (RT_FAILURE(rc))
244 break;
245
246 rc = RTFileWrite(file, pvBuf, cbSize, 0);
247 }
248 }while (0);
249
250 RTFileClose(file);
251
252 /* Cleanup */
253 if (pvBuf)
254 RTMemFree(pvBuf);
255 if (paFiles)
256 {
257 for (size_t i = 0; i < cFiles; ++i)
258 if (paFiles[i].pszTestDigest)
259 RTStrFree((char*)paFiles[i].pszTestDigest);
260 RTMemFree(paFiles);
261 }
262
263 /* Delete the manifest file on failure */
264 if (RT_FAILURE(rc))
265 RTFileDelete(pszManifestFile);
266
267 return rc;
268}
269
270
271RTR3DECL(int) RTManifestVerifyDigestType(void *pvBuf, size_t cbSize, RTDIGESTTYPE &digestType)
272{
273 /* Validate input */
274 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
275 AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
276
277 int rc = VINF_SUCCESS;
278
279 char *pcBuf = (char*)pvBuf;
280 size_t cbRead = 0;
281 /* Parse the manifest file line by line */
282 for (;;)
283 {
284 if (cbRead >= cbSize)
285 {
286 digestType = RTDIGESTTYPE_UNKNOWN;
287 rc = VERR_MANIFEST_UNSUPPORTED_DIGEST_TYPE;
288 break;
289 }
290
291 size_t cch = rtManifestIndexOfCharInBuf(pcBuf, cbSize - cbRead, '\n') + 1;
292
293 /* Skip empty lines (UNIX/DOS format) */
294 if ( ( cch == 1
295 && pcBuf[0] == '\n')
296 || ( cch == 2
297 && pcBuf[0] == '\r'
298 && pcBuf[1] == '\n'))
299 {
300 pcBuf += cch;
301 cbRead += cch;
302 continue;
303 }
304
305 /* Check for the digest algorithm */
306 if (pcBuf[0] == 'S'
307 && pcBuf[1] == 'H'
308 && pcBuf[2] == 'A'
309 && pcBuf[3] == '1')
310 {
311 digestType = RTDIGESTTYPE_SHA1;
312 break;
313 }
314 else if (pcBuf[0] == 'S'
315 && pcBuf[1] == 'H'
316 && pcBuf[2] == 'A'
317 && pcBuf[3] == '2'
318 && pcBuf[4] == '5'
319 && pcBuf[5] == '6')
320 {
321 digestType = RTDIGESTTYPE_SHA256;
322 break;
323 }
324 }
325
326 return rc;
327}
328
329RTR3DECL(int) RTManifestVerifyFilesBuf(void *pvBuf, size_t cbSize, PRTMANIFESTTEST paTests, size_t cTests, size_t *piFailed)
330{
331 /* Validate input */
332 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
333 AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
334 AssertPtrReturn(paTests, VERR_INVALID_POINTER);
335 AssertReturn(cTests > 0, VERR_INVALID_PARAMETER);
336 AssertPtrNullReturn(piFailed, VERR_INVALID_POINTER);
337
338 int rc = VINF_SUCCESS;
339
340 PRTMANIFESTFILEENTRY paFiles = (PRTMANIFESTFILEENTRY)RTMemTmpAllocZ(sizeof(RTMANIFESTFILEENTRY) * cTests);
341 if (!paFiles)
342 return VERR_NO_MEMORY;
343
344 /* Fill our compare list */
345 for (size_t i = 0; i < cTests; ++i)
346 paFiles[i].pTestPattern = &paTests[i];
347
348 char *pcBuf = (char*)pvBuf;
349 size_t cbRead = 0;
350 /* Parse the manifest file line by line */
351 for (;;)
352 {
353 if (cbRead >= cbSize)
354 break;
355
356 size_t cch = rtManifestIndexOfCharInBuf(pcBuf, cbSize - cbRead, '\n') + 1;
357
358 /* Skip empty lines (UNIX/DOS format) */
359 if ( ( cch == 1
360 && pcBuf[0] == '\n')
361 || ( cch == 2
362 && pcBuf[0] == '\r'
363 && pcBuf[1] == '\n'))
364 {
365 pcBuf += cch;
366 cbRead += cch;
367 continue;
368 }
369
370 /** @todo r=bird:
371 * -# Better deal with this EOF line platform dependency
372 * -# The SHA1 test should probably include a blank space check.
373 * -# If there is a specific order to the elements in the string, it would be
374 * good if the delimiter searching checked for it.
375 * -# Deal with filenames containing delimiter characters.
376 */
377
378 /* Check for the digest algorithm */
379 if ( cch < 4 ||
380 (!( pcBuf[0] == 'S'
381 && pcBuf[1] == 'H'
382 && pcBuf[2] == 'A'
383 && pcBuf[3] == '1')
384 &&
385 !( pcBuf[0] == 'S'
386 && pcBuf[1] == 'H'
387 && pcBuf[2] == 'A'
388 && pcBuf[3] == '2'
389 && pcBuf[4] == '5'
390 && pcBuf[5] == '6'))
391 )
392 {
393 /* Digest unsupported */
394 rc = VERR_MANIFEST_UNSUPPORTED_DIGEST_TYPE;
395 break;
396 }
397
398 /* Try to find the filename */
399 char *pszNameStart = rtManifestPosOfCharInBuf(pcBuf, cch, '(');
400 if (!pszNameStart)
401 {
402 rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
403 break;
404 }
405 char *pszNameEnd = rtManifestPosOfCharInBuf(pcBuf, cch, ')');
406 if (!pszNameEnd)
407 {
408 rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
409 break;
410 }
411
412 /* Copy the filename part */
413 size_t cchName = pszNameEnd - pszNameStart - 1;
414 char *pszName = (char *)RTMemTmpAlloc(cchName + 1);
415 if (!pszName)
416 {
417 rc = VERR_NO_MEMORY;
418 break;
419 }
420 memcpy(pszName, pszNameStart + 1, cchName);
421 pszName[cchName] = '\0';
422
423 /* Try to find the digest sum */
424 char *pszDigestStart = rtManifestPosOfCharInBuf(pcBuf, cch, '=') + 1;
425 if (!pszDigestStart)
426 {
427 RTMemTmpFree(pszName);
428 rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
429 break;
430 }
431 char *pszDigestEnd = rtManifestPosOfCharInBuf(pcBuf, cch, '\r');
432 if (!pszDigestEnd)
433 pszDigestEnd = rtManifestPosOfCharInBuf(pcBuf, cch, '\n');
434 if (!pszDigestEnd)
435 {
436 rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
437 break;
438 }
439 /* Copy the digest part */
440 size_t cchDigest = pszDigestEnd - pszDigestStart - 1;
441 char *pszDigest = (char *)RTMemTmpAlloc(cchDigest + 1);
442 if (!pszDigest)
443 {
444 rc = VERR_NO_MEMORY;
445 break;
446 }
447 memcpy(pszDigest, pszDigestStart + 1, cchDigest);
448 pszDigest[cchDigest] = '\0';
449
450 /* Check our file list against the extracted data */
451 bool fFound = false;
452 for (size_t i = 0; i < cTests; ++i)
453 {
454 if (!RTStrCmp(RTPathFilename(paFiles[i].pTestPattern->pszTestFile), RTStrStrip(pszName)))
455 {
456 /* Add the data of the manifest file to the file list */
457 paFiles[i].pszManifestFile = RTStrDup(RTStrStrip(pszName));
458 paFiles[i].pszManifestDigest = RTStrDup(RTStrStrip(pszDigest));
459 fFound = true;
460 break;
461 }
462 }
463 RTMemTmpFree(pszName);
464 RTMemTmpFree(pszDigest);
465 if (!fFound)
466 {
467 /* There have to be an entry in the file list */
468 rc = VERR_MANIFEST_FILE_MISMATCH;
469 break;
470 }
471
472 pcBuf += cch;
473 cbRead += cch;
474 }
475
476 if ( rc == VINF_SUCCESS
477 || rc == VERR_EOF)
478 {
479 rc = VINF_SUCCESS;
480 for (size_t i = 0; i < cTests; ++i)
481 {
482 /* If there is an entry in the file list, which hasn't an
483 * equivalent in the manifest file, its an error. */
484 if ( !paFiles[i].pszManifestFile
485 || !paFiles[i].pszManifestDigest)
486 {
487 rc = VERR_MANIFEST_FILE_MISMATCH;
488 break;
489 }
490
491 /* Do the manifest SHA1 digest match against the actual digest? */
492 if (RTStrICmp(paFiles[i].pszManifestDigest, paFiles[i].pTestPattern->pszTestDigest))
493 {
494 if (piFailed)
495 *piFailed = i;
496 rc = VERR_MANIFEST_DIGEST_MISMATCH;
497 break;
498 }
499 }
500 }
501
502 /* Cleanup */
503 for (size_t i = 0; i < cTests; ++i)
504 {
505 if (paFiles[i].pszManifestFile)
506 RTStrFree(paFiles[i].pszManifestFile);
507 if (paFiles[i].pszManifestDigest)
508 RTStrFree(paFiles[i].pszManifestDigest);
509 }
510 RTMemTmpFree(paFiles);
511
512 RTPrintf("rc = %Rrc\n", rc);
513 return rc;
514}
515
516RTR3DECL(int) RTManifestWriteFilesBuf(void **ppvBuf, size_t *pcbSize, RTDIGESTTYPE enmDigestType, PRTMANIFESTTEST paFiles, size_t cFiles)
517{
518 /* Validate input */
519 AssertPtrReturn(ppvBuf, VERR_INVALID_POINTER);
520 AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
521 AssertPtrReturn(paFiles, VERR_INVALID_POINTER);
522 AssertReturn(cFiles > 0, VERR_INVALID_PARAMETER);
523
524 const char *pcszDigestType;
525 switch (enmDigestType)
526 {
527 case RTDIGESTTYPE_CRC32: pcszDigestType = "CRC32"; break;
528 case RTDIGESTTYPE_CRC64: pcszDigestType = "CRC64"; break;
529 case RTDIGESTTYPE_MD5: pcszDigestType = "MD5"; break;
530 case RTDIGESTTYPE_SHA1: pcszDigestType = "SHA1"; break;
531 case RTDIGESTTYPE_SHA256: pcszDigestType = "SHA256"; break;
532 default: return VERR_INVALID_PARAMETER;
533 }
534
535 /* Calculate the size necessary for the memory buffer. */
536 size_t cbSize = 0;
537 size_t cbMaxSize = 0;
538 for (size_t i = 0; i < cFiles; ++i)
539 {
540 size_t cbTmp = strlen(RTPathFilename(paFiles[i].pszTestFile))
541 + strlen(paFiles[i].pszTestDigest)
542 + strlen(pcszDigestType)
543 + 6;
544 cbMaxSize = RT_MAX(cbMaxSize, cbTmp);
545 cbSize += cbTmp;
546 }
547
548 /* Create the memory buffer */
549 void *pvBuf = RTMemAlloc(cbSize);
550 if (!pvBuf)
551 return VERR_NO_MEMORY;
552
553 /* Allocate a temporary string buffer. */
554 char *pszTmp = RTStrAlloc(cbMaxSize + 1);
555 if (!pszTmp)
556 {
557 RTMemFree(pvBuf);
558 return VERR_NO_MEMORY;
559 }
560 size_t cbPos = 0;
561
562 for (size_t i = 0; i < cFiles; ++i)
563 {
564 size_t cch = RTStrPrintf(pszTmp, cbMaxSize + 1, "%s (%s)= %s\n", pcszDigestType, RTPathFilename(paFiles[i].pszTestFile), paFiles[i].pszTestDigest);
565 memcpy(&((char*)pvBuf)[cbPos], pszTmp, cch);
566 cbPos += cch;
567 }
568 RTStrFree(pszTmp);
569
570 /* Results */
571 *ppvBuf = pvBuf;
572 *pcbSize = cbSize;
573
574 return VINF_SUCCESS;
575}
576
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette