/** * Verifies the manifest and its signature. * * @returns VBox status code, failures with message. * @param hOurManifest The manifest we compiled. * @param hManifestFile The manifest file in the extension pack. * @param hSignatureFile The manifest signature file. * @param pszError Where to store an error message on failure. * @param cbError The size of the buffer @a pszError points to. */ static int vboxExtPackVerifyManifestAndSignature(RTMANIFEST hOurManifest, RTVFSFILE hManifestFile, RTVFSFILE hSignatureFile, char *pszError, size_t cbError) { /* * Read the manifest from the extension pack. */ int rc = RTVfsFileSeek(hManifestFile, 0, RTFILE_SEEK_BEGIN, NULL); if (RT_FAILURE(rc)) return vboxExtPackReturnError(rc, pszError, cbError, "RTVfsFileSeek failed: %Rrc", rc); RTMANIFEST hTheirManifest; rc = RTManifestCreate(0 /*fFlags*/, &hTheirManifest); if (RT_FAILURE(rc)) return vboxExtPackReturnError(rc, pszError, cbError, "RTManifestCreate failed: %Rrc", rc); RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hManifestFile); rc = RTManifestReadStandard(hTheirManifest, hVfsIos); RTVfsIoStrmRelease(hVfsIos); if (RT_SUCCESS(rc)) { /* * Compare the manifests. */ static const char *s_apszIgnoreEntries[] = { VBOX_EXTPACK_MANIFEST_NAME, VBOX_EXTPACK_SIGNATURE_NAME, "./" VBOX_EXTPACK_MANIFEST_NAME, "./" VBOX_EXTPACK_SIGNATURE_NAME, NULL }; char szError[RTPATH_MAX]; rc = RTManifestEqualsEx(hOurManifest, hTheirManifest, &s_apszIgnoreEntries[0], NULL, RTMANIFEST_EQUALS_IGN_MISSING_ATTRS /*fFlags*/, szError, sizeof(szError)); if (RT_SUCCESS(rc)) { /* * Validate the manifest file signature. */ /** @todo implement signature stuff */ NOREF(hSignatureFile); } else if (rc == VERR_NOT_EQUAL && szError[0]) vboxExtPackSetError(pszError, cbError, "Manifest mismatch: %s", szError); else vboxExtPackSetError(pszError, cbError, "RTManifestEqualsEx failed: %Rrc", rc); #if 0 RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM; RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut); RTVfsIoStrmWrite(hVfsIosStdOut, "Our:\n", sizeof("Our:\n") - 1, true, NULL); RTManifestWriteStandard(hOurManifest, hVfsIosStdOut); RTVfsIoStrmWrite(hVfsIosStdOut, "Their:\n", sizeof("Their:\n") - 1, true, NULL); RTManifestWriteStandard(hTheirManifest, hVfsIosStdOut); #endif } else vboxExtPackSetError(pszError, cbError, "Error parsing '%s': %Rrc", VBOX_EXTPACK_MANIFEST_NAME, rc); RTManifestRelease(hTheirManifest); return rc; }
/** * Validates the extension pack tarball prior to unpacking. * * Operations performed: * - Mandatory files. * - Manifest check. * - Manifest seal check. * - XML check, match name. * * @returns VBox status code, failures with message. * @param hTarballFile The handle to open the @a pszTarball file. * @param pszExtPackName The name of the extension pack name. NULL if * the name is not fixed. * @param pszTarball The name of the tarball in case we have to * complain about something. * @param pszTarballDigest The SHA-256 digest of the tarball. Empty string * if no digest available. * @param pszError Where to store an error message on failure. * @param cbError The size of the buffer @a pszError points to. * @param phValidManifest Where to optionally return the handle to fully * validated the manifest for the extension pack. * This includes all files. * @param phXmlFile Where to optionally return the memorized XML * file. * @param pStrDigest Where to return the digest of the file. * Optional. */ int VBoxExtPackValidateTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball, const char *pszTarballDigest, char *pszError, size_t cbError, PRTMANIFEST phValidManifest, PRTVFSFILE phXmlFile, RTCString *pStrDigest) { /* * Clear return values. */ if (phValidManifest) *phValidManifest = NIL_RTMANIFEST; if (phXmlFile) *phXmlFile = NIL_RTVFSFILE; Assert(cbError > 1); *pszError = '\0'; NOREF(pszTarball); /* * Open the tar.gz filesystem stream and set up an manifest in-memory file. */ RTMANIFEST hFileManifest; RTVFSFSSTREAM hTarFss; int rc = VBoxExtPackOpenTarFss(hTarballFile, pszError, cbError, &hTarFss, &hFileManifest); if (RT_FAILURE(rc)) return rc; RTMANIFEST hOurManifest; rc = RTManifestCreate(0 /*fFlags*/, &hOurManifest); if (RT_SUCCESS(rc)) { /* * Process the tarball (would be nice to move this to a function). */ RTVFSFILE hXmlFile = NIL_RTVFSFILE; RTVFSFILE hManifestFile = NIL_RTVFSFILE; RTVFSFILE hSignatureFile = NIL_RTVFSFILE; for (;;) { /* * Get the next stream object. */ char *pszName; RTVFSOBJ hVfsObj; RTVFSOBJTYPE enmType; rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj); if (RT_FAILURE(rc)) { if (rc != VERR_EOF) vboxExtPackSetError(pszError, cbError, "RTVfsFsStrmNext failed: %Rrc", rc); else rc = VINF_SUCCESS; break; } const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName; /* * Check the type & name validity, performing special tests on * standard extension pack member files. * * N.B. We will always reach the end of the loop before breaking on * failure - cleanup reasons. */ rc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, pszError, cbError); if (RT_SUCCESS(rc)) { PRTVFSFILE phVfsFile = NULL; if (!strcmp(pszAdjName, VBOX_EXTPACK_DESCRIPTION_NAME)) phVfsFile = &hXmlFile; else if (!strcmp(pszAdjName, VBOX_EXTPACK_MANIFEST_NAME)) phVfsFile = &hManifestFile; else if (!strcmp(pszAdjName, VBOX_EXTPACK_SIGNATURE_NAME)) phVfsFile = &hSignatureFile; else if (!strncmp(pszAdjName, VBOX_EXTPACK_LICENSE_NAME_PREFIX, sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX) - 1)) rc = VBoxExtPackValidateStandardFile(pszAdjName, enmType, &hVfsObj, NULL, pszError, cbError); if (phVfsFile) rc = VBoxExtPackValidateStandardFile(pszAdjName, enmType, &hVfsObj, phVfsFile, pszError, cbError); } /* * Add any I/O stream to the manifest */ if ( RT_SUCCESS(rc) && ( enmType == RTVFSOBJTYPE_FILE || enmType == RTVFSOBJTYPE_IO_STREAM)) { RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj); rc = RTManifestEntryAddIoStream(hOurManifest, hVfsIos, pszAdjName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256); if (RT_FAILURE(rc)) vboxExtPackSetError(pszError, cbError, "RTManifestEntryAddIoStream failed on '%s': %Rrc", pszAdjName, rc); RTVfsIoStrmRelease(hVfsIos); } /* * Clean up and break out on failure. */ RTVfsObjRelease(hVfsObj); RTStrFree(pszName); if (RT_FAILURE(rc)) break; } /* * Check the integrity of the tarball file. */ if (RT_SUCCESS(rc)) { RTVfsFsStrmRelease(hTarFss); hTarFss = NIL_RTVFSFSSTREAM; rc = vboxExtPackVerifyFileDigest(hFileManifest, pszTarballDigest, pStrDigest, pszError, cbError); } /* * If we've successfully processed the tarball, verify that the * mandatory files are present. */ if (RT_SUCCESS(rc)) { if (hXmlFile == NIL_RTVFSFILE) rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_DESCRIPTION_NAME); if (hManifestFile == NIL_RTVFSFILE) rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_MANIFEST_NAME); if (hSignatureFile == NIL_RTVFSFILE) rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_SIGNATURE_NAME); } /* * Check the manifest and it's signature. */ if (RT_SUCCESS(rc)) rc = vboxExtPackVerifyManifestAndSignature(hOurManifest, hManifestFile, hSignatureFile, pszError, cbError); /* * Check the XML. */ if (RT_SUCCESS(rc)) rc = vboxExtPackVerifyXml(hXmlFile, pszExtPackName, pszError, cbError); /* * Returns objects. */ if (RT_SUCCESS(rc)) { if (phValidManifest) { RTManifestRetain(hOurManifest); *phValidManifest = hOurManifest; } if (phXmlFile) { RTVfsFileRetain(hXmlFile); *phXmlFile = hXmlFile; } } /* * Release our object references. */ RTManifestRelease(hOurManifest); RTVfsFileRelease(hXmlFile); RTVfsFileRelease(hManifestFile); RTVfsFileRelease(hSignatureFile); } else vboxExtPackSetError(pszError, cbError, "RTManifestCreate failed: %Rrc", rc); RTVfsFsStrmRelease(hTarFss); RTManifestRelease(hFileManifest); return rc; }
/** * Verify a manifest. * * @returns Program exit code, failures with error message. * @param pszManifest The manifest file. NULL if standard input. * @param fStdFormat Whether to expect standard format (true) or * java format (false). * @param pszChDir The directory to change into before processing * the files in the manifest. */ static RTEXITCODE rtManifestDoVerify(const char *pszManifest, bool fStdFormat, const char *pszChDir) { /* * Open the manifest. */ int rc; RTVFSIOSTREAM hVfsIos; if (!pszManifest) { rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT, RTFILE_O_READ, false /*fLeaveOpen*/, &hVfsIos); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard input for reading: %Rrc", rc); } else { const char *pszError; rc = RTVfsChainOpenIoStream(pszManifest, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, &hVfsIos, &pszError); if (RT_FAILURE(rc)) { if (pszError && *pszError) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsChainOpenIoStream failed with rc=%Rrc:\n" " '%s'\n", " %*s^\n", rc, pszManifest, pszError - pszManifest, ""); return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed with %Rrc opening the input manifest '%s'", rc, pszManifest); } } /* * Read it. */ RTMANIFEST hManifest; rc = RTManifestCreate(0 /*fFlags*/, &hManifest); if (RT_SUCCESS(rc)) { if (fStdFormat) { char szErr[4096 + 1024]; rc = RTManifestReadStandardEx(hManifest, hVfsIos, szErr, sizeof(szErr)); if (RT_SUCCESS(rc)) { RTVfsIoStrmRelease(hVfsIos); hVfsIos = NIL_RTVFSIOSTREAM; /* * Do the verification. */ /** @todo We're missing some enumeration APIs here! */ RTMsgError("The manifest read fine, but the actual verification code is yet to be written. Sorry."); rc = VERR_NOT_IMPLEMENTED; #if 1 /* For now, just write the manifest to stdout so we can test the read routine. */ RTVFSIOSTREAM hVfsIosOut; rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, false /*fLeaveOpen*/, &hVfsIosOut); if (RT_SUCCESS(rc)) { RTManifestWriteStandard(hManifest, hVfsIosOut); RTVfsIoStrmRelease(hVfsIosOut); } #endif } else if (szErr[0]) RTMsgError("Error reading manifest: %s", szErr); else RTMsgError("Error reading manifest: %Rrc", rc); } else { RTMsgError("Support for Java manifest files is not implemented yet"); rc = VERR_NOT_IMPLEMENTED; } RTManifestRelease(hManifest); } RTVfsIoStrmRelease(hVfsIos); return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; }
/** * Rewinds the tarball file handle and creates a gunzip | tar chain that * results in a filesystem stream. * * @returns VBox status code, failures with message. * @param hTarballFile The handle to the tarball file. * @param pszError Where to store an error message on failure. * @param cbError The size of the buffer @a pszError points to. * @param phTarFss Where to return the filesystem stream handle. * @param phFileManifest Where to return a manifest where the tarball is * gettting hashed. The entry will be called * "extpack" and be ready when the file system * stream is at an end. Optional. */ int VBoxExtPackOpenTarFss(RTFILE hTarballFile, char *pszError, size_t cbError, PRTVFSFSSTREAM phTarFss, PRTMANIFEST phFileManifest) { Assert(cbError > 0); *pszError = '\0'; *phTarFss = NIL_RTVFSFSSTREAM; /* * Rewind the file and set up a VFS chain for it. */ int rc = RTFileSeek(hTarballFile, 0, RTFILE_SEEK_BEGIN, NULL); if (RT_FAILURE(rc)) return vboxExtPackReturnError(rc, pszError, cbError, "Failed seeking to the start of the tarball: %Rrc", rc); RTVFSIOSTREAM hTarballIos; rc = RTVfsIoStrmFromRTFile(hTarballFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, true /*fLeaveOpen*/, &hTarballIos); if (RT_FAILURE(rc)) return vboxExtPackReturnError(rc, pszError, cbError, "RTVfsIoStrmFromRTFile failed: %Rrc", rc); RTMANIFEST hFileManifest = NIL_RTMANIFEST; rc = RTManifestCreate(0 /*fFlags*/, &hFileManifest); if (RT_SUCCESS(rc)) { RTVFSIOSTREAM hPtIos; rc = RTManifestEntryAddPassthruIoStream(hFileManifest, hTarballIos, "extpack", RTMANIFEST_ATTR_SHA256, true /*read*/, &hPtIos); if (RT_SUCCESS(rc)) { RTVFSIOSTREAM hGunzipIos; rc = RTZipGzipDecompressIoStream(hPtIos, 0 /*fFlags*/, &hGunzipIos); if (RT_SUCCESS(rc)) { RTVFSFSSTREAM hTarFss; rc = RTZipTarFsStreamFromIoStream(hGunzipIos, 0 /*fFlags*/, &hTarFss); if (RT_SUCCESS(rc)) { RTVfsIoStrmRelease(hPtIos); RTVfsIoStrmRelease(hGunzipIos); RTVfsIoStrmRelease(hTarballIos); *phTarFss = hTarFss; if (phFileManifest) *phFileManifest = hFileManifest; else RTManifestRelease(hFileManifest); return VINF_SUCCESS; } vboxExtPackSetError(pszError, cbError, "RTZipTarFsStreamFromIoStream failed: %Rrc", rc); RTVfsIoStrmRelease(hGunzipIos); } else vboxExtPackSetError(pszError, cbError, "RTZipGzipDecompressIoStream failed: %Rrc", rc); RTVfsIoStrmRelease(hPtIos); } else vboxExtPackSetError(pszError, cbError, "RTManifestEntryAddPassthruIoStream failed: %Rrc", rc); RTManifestRelease(hFileManifest); } else vboxExtPackSetError(pszError, cbError, "RTManifestCreate failed: %Rrc", rc); RTVfsIoStrmRelease(hTarballIos); return rc; }
/** * Create a manifest from the specified input files. * * @returns Program exit code, failures with error message. * @param pszManifest The name of the output manifest file. NULL if * it should be written to standard output. * @param fStdFormat Whether to expect standard format (true) or * java format (false). * @param pszChDir The directory to change into before processing * the file arguments. * @param fAttr The file attributes to put in the manifest. * @param pGetState The RTGetOpt state. * @param pUnion What the last RTGetOpt() call returned. * @param chOpt What the last RTGetOpt() call returned. */ static RTEXITCODE rtManifestDoCreate(const char *pszManifest, bool fStdFormat, const char *pszChDir, uint32_t fAttr, PRTGETOPTSTATE pGetState, PRTGETOPTUNION pUnion, int chOpt) { /* * Open the manifest file. */ int rc; RTVFSIOSTREAM hVfsIos; if (!pszManifest) { rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, false /*fLeaveOpen*/, &hVfsIos); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard output for writing: %Rrc", rc); } else { const char *pszError; rc = RTVfsChainOpenIoStream(pszManifest, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE, &hVfsIos, &pszError); if (RT_FAILURE(rc)) { if (pszError && *pszError) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsChainOpenIoStream failed with rc=%Rrc:\n" " '%s'\n", " %*s^\n", rc, pszManifest, pszError - pszManifest, ""); return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed with %Rrc opening the manifest '%s'", rc, pszManifest); } } /* * Create the internal manifest. */ RTMANIFEST hManifest; rc = RTManifestCreate(0 /*fFlags*/, &hManifest); if (RT_SUCCESS(rc)) { /* * Change directory and start processing the specified files. */ if (pszChDir) { rc = RTPathSetCurrent(pszChDir); if (RT_FAILURE(rc)) RTMsgError("Failed to change directory to '%s': %Rrc", pszChDir, rc); } if (RT_SUCCESS(rc)) { while (chOpt == VINF_GETOPT_NOT_OPTION) { rc = rtManifestAddFileToManifest(hManifest, pUnion->psz, fAttr); if (RT_FAILURE(rc)) break; /* next */ chOpt = RTGetOpt(pGetState, pUnion); } if (RT_SUCCESS(rc) && chOpt != 0) { RTGetOptPrintError(chOpt, pUnion); rc = chOpt < 0 ? chOpt : -chOpt; } } /* * Write the manifest. */ if (RT_SUCCESS(rc)) { if (fStdFormat) { rc = RTManifestWriteStandard(hManifest, hVfsIos); if (RT_FAILURE(rc)) RTMsgError("RTManifestWriteStandard failed: %Rrc", rc); } else { RTMsgError("Support for Java manifest files is not implemented yet"); rc = VERR_NOT_IMPLEMENTED; } } RTManifestRelease(hManifest); } RTVfsIoStrmRelease(hVfsIos); return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; }