diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Runtime/common/crypto/pemfile.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Runtime/common/crypto/pemfile.cpp | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/crypto/pemfile.cpp b/src/VBox/Runtime/common/crypto/pemfile.cpp new file mode 100644 index 00000000..b23c3b44 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pemfile.cpp @@ -0,0 +1,652 @@ +/* $Id: pemfile.cpp $ */ +/** @file + * IPRT - Crypto - PEM file reader / writer. + * + * See RFC-1341 for the original ideas for the format, but keep in mind + * that the format was hijacked and put to different uses. We're aiming at + * dealing with the different uses rather than anything email related here. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pem.h> + +#include <iprt/asm.h> +#include <iprt/base64.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/memsafer.h> +#include <iprt/file.h> +#include <iprt/string.h> + + + +/** + * Looks for a PEM-like marker. + * + * @returns true if found, false if not. + * @param pbContent Start of the content to search thru. + * @param cbContent The size of the content to search. + * @param offStart The offset into pbContent to start searching. + * @param pszLeadWord The lead word (BEGIN/END). + * @param cchLeadWord The length of the lead word. + * @param paMarkers Pointer to an array of markers. + * @param cMarkers Number of markers in the array. + * @param ppMatch Where to return the pointer to the matching + * marker. Optional. + * @param poffBegin Where to return the start offset of the marker. + * Optional. + * @param poffEnd Where to return the end offset of the marker + * (trailing whitespace and newlines will be + * skipped). Optional. + */ +static bool rtCrPemFindMarker(uint8_t const *pbContent, size_t cbContent, size_t offStart, + const char *pszLeadWord, size_t cchLeadWord, PCRTCRPEMMARKER paMarkers, size_t cMarkers, + PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd) +{ + /* Remember the start of the content for the purpose of calculating offsets. */ + uint8_t const * const pbStart = pbContent; + + /* Skip adhead by offStart */ + if (offStart >= cbContent) + return false; + pbContent += offStart; + cbContent -= offStart; + + /* + * Search the content. + */ + while (cbContent > 6) + { + /* + * Look for dashes. + */ + uint8_t const *pbStartSearch = pbContent; + pbContent = (uint8_t const *)memchr(pbContent, '-', cbContent); + if (!pbContent) + break; + + cbContent -= pbContent - pbStartSearch; + if (cbContent < 6) + break; + + /* + * There must be at least three to interest us. + */ + if ( pbContent[1] == '-' + && pbContent[2] == '-') + { + unsigned cDashes = 3; + while (cDashes < cbContent && pbContent[cDashes] == '-') + cDashes++; + + if (poffBegin) + *poffBegin = pbContent - pbStart; + cbContent -= cDashes; + pbContent += cDashes; + + /* + * Match lead word. + */ + if ( cbContent > cchLeadWord + && memcmp(pbContent, pszLeadWord, cchLeadWord) == 0 + && RT_C_IS_BLANK(pbContent[cchLeadWord]) ) + { + pbContent += cchLeadWord; + cbContent -= cchLeadWord; + while (cbContent > 0 && RT_C_IS_BLANK(*pbContent)) + { + pbContent++; + cbContent--; + } + + /* + * Match one of the specified markers. + */ + uint8_t const *pbSavedContent = pbContent; + size_t const cbSavedContent = cbContent; + for (uint32_t iMarker = 0; iMarker < cMarkers; iMarker++) + { + pbContent = pbSavedContent; + cbContent = cbSavedContent; + + uint32_t cWords = paMarkers[iMarker].cWords; + PCRTCRPEMMARKERWORD pWord = paMarkers[iMarker].paWords; + while (cWords > 0) + { + uint32_t const cchWord = pWord->cchWord; + if (cbContent <= cchWord) + break; + if (memcmp(pbContent, pWord->pszWord, cchWord)) + break; + pbContent += cchWord; + cbContent -= cchWord; + + if (!cbContent) + break; + if (RT_C_IS_BLANK(*pbContent)) + do + { + pbContent++; + cbContent--; + } while (cbContent > 0 && RT_C_IS_BLANK(*pbContent)); + else if (cWords > 1 || pbContent[0] != '-') + break; + + cWords--; + if (cWords == 0) + { + /* + * If there are three or more dashes following now, we've got a hit. + */ + if ( cbContent > 3 + && pbContent[0] == '-' + && pbContent[1] == '-' + && pbContent[2] == '-') + { + cDashes = 3; + while (cDashes < cbContent && pbContent[cDashes] == '-') + cDashes++; + cbContent -= cDashes; + pbContent += cDashes; + + /* + * Skip spaces and newline. + */ + while (cbContent > 0 && RT_C_IS_SPACE(*pbContent)) + pbContent++, cbContent--; + if (poffEnd) + *poffEnd = pbContent - pbStart; + if (ppMatch) + *ppMatch = &paMarkers[iMarker]; + return true; + } + break; + } + pWord++; + } /* for each word in marker. */ + } /* for each marker. */ + } + } + else + { + pbContent++; + cbContent--; + } + } + + return false; +} + + +static bool rtCrPemFindMarkerSection(uint8_t const *pbContent, size_t cbContent, size_t offStart, + PCRTCRPEMMARKER paMarkers, size_t cMarkers, + PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd, size_t *poffResume) +{ + /** @todo Detect BEGIN / END mismatch. */ + PCRTCRPEMMARKER pMatch; + if (rtCrPemFindMarker(pbContent, cbContent, offStart, "BEGIN", 5, paMarkers, cMarkers, + &pMatch, NULL /*poffStart*/, poffBegin)) + { + if (rtCrPemFindMarker(pbContent, cbContent, *poffBegin, "END", 3, pMatch, 1, + NULL /*ppMatch*/, poffEnd, poffResume)) + { + *ppMatch = pMatch; + return true; + } + } + *ppMatch = NULL; + return false; +} + + +/** + * Parses any fields the message may contain. + * + * @retval VINF_SUCCESS + * @retval VERR_NO_MEMORY + * @retval VERR_CR_MALFORMED_PEM_HEADER + * + * @param pSection The current section, where we will attach a list of + * fields to the pFieldHead member. + * @param pbContent The content of the PEM message being parsed. + * @param cbContent The length of the PEM message. + * @param pcbFields Where to return the length of the header fields we found. + */ +static int rtCrPemProcessFields(PRTCRPEMSECTION pSection, uint8_t const *pbContent, size_t cbContent, size_t *pcbFields) +{ + uint8_t const * const pbContentStart = pbContent; + + /* + * Work the encapulated header protion field by field. + * + * This is optional, so currently we don't throw errors here but leave that + * to when we work the text portion with the base64 decoder. Also, as a reader + * we don't go all pedanic on confirming to specification (RFC-1421), especially + * given that it's used for crypto certificates, keys and the like not email. :-) + */ + PCRTCRPEMFIELD *ppNext = &pSection->pFieldHead; + while (cbContent > 0) + { + /* Just look for a colon first. */ + const uint8_t *pbColon = (const uint8_t *)memchr(pbContent, ':', cbContent); + if (!pbColon) + break; + size_t offColon = pbColon - pbContent; + + /* Check that the colon is within the first line. */ + if (!memchr(pbContent, '\n', cbContent - offColon)) + return VERR_CR_MALFORMED_PEM_HEADER; + + /* Skip leading spaces (there shouldn't be any, but just in case). */ + while (RT_C_IS_BLANK(*pbContent) && /*paranoia:*/ offColon > 0) + { + offColon--; + cbContent--; + pbContent++; + } + + /* There shouldn't be any spaces before the colon, but just in case */ + size_t cchName = offColon; + while (cchName > 0 && RT_C_IS_BLANK(pbContent[cchName - 1])) + cchName--; + + /* Skip leading value spaces (there typically is at least one). */ + size_t offValue = offColon + 1; + while (offValue < cbContent && RT_C_IS_BLANK(pbContent[offValue])) + offValue++; + + /* Find the newline the field value ends with and where the next iteration should start later on. */ + size_t cbLeft; + uint8_t const *pbNext = (uint8_t const *)memchr(&pbContent[offValue], '\n', cbContent - offValue); + while ( pbNext + && (cbLeft = pbNext - pbContent) < cbContent + && RT_C_IS_BLANK(pbNext[1]) /* next line must start with a space or tab */) + pbNext = (uint8_t const *)memchr(&pbNext[1], '\n', cbLeft - 1); + + size_t cchValue; + if (pbNext) + { + cchValue = pbNext - &pbContent[offValue]; + if (cchValue > 0 && pbNext[-1] == '\r') + cchValue--; + pbNext++; + } + else + { + cchValue = cbContent - offValue; + pbNext = &pbContent[cbContent]; + } + + /* Strip trailing spaces. */ + while (cchValue > 0 && RT_C_IS_BLANK(pbContent[offValue + cchValue - 1])) + cchValue--; + + /* + * Allocate a field instance. + * + * Note! We don't consider field data sensitive at the moment. This + * mainly because the fields are chiefly used to indicate the + * encryption parameters to the body. + */ + PRTCRPEMFIELD pNewField = (PRTCRPEMFIELD)RTMemAllocZVar(sizeof(*pNewField) + cchName + 1 + cchValue + 1); + if (!pNewField) + return VERR_NO_MEMORY; + pNewField->cchName = cchName; + pNewField->cchValue = cchValue; + memcpy(pNewField->szName, pbContent, cchName); + pNewField->szName[cchName] = '\0'; + char *pszDst = (char *)memcpy(&pNewField->szName[cchName + 1], &pbContent[offValue], cchValue); + pNewField->pszValue = pszDst; + pszDst[cchValue] = '\0'; + pNewField->pNext = NULL; + + *ppNext = pNewField; + ppNext = &pNewField->pNext; + + /* + * Advance past the field. + */ + cbContent -= pbNext - pbContent; + pbContent = pbNext; + } + + /* + * Skip blank line(s) before the body. + */ + while (cbContent >= 1) + { + size_t cbSkip; + if (pbContent[0] == '\n') + cbSkip = 1; + else if ( pbContent[0] == '\r' + && cbContent >= 2 + && pbContent[1] == '\n') + cbSkip = 2; + else + break; + pbContent += cbSkip; + cbContent -= cbSkip; + } + + *pcbFields = pbContent - pbContentStart; + return VINF_SUCCESS; +} + + +/** + * Does the decoding of a PEM-like data blob after it has been located. + * + * @returns IPRT status ocde + * @param pbContent The start of the PEM-like content (text). + * @param cbContent The max size of the PEM-like content. + * @param fSensitive Set if the safer allocator should be used. + * @param ppvDecoded Where to return a heap block containing the + * decoded content. + * @param pcbDecoded Where to return the size of the decoded content. + */ +static int rtCrPemDecodeBase64(uint8_t const *pbContent, size_t cbContent, bool fSensitive, + void **ppvDecoded, size_t *pcbDecoded) +{ + ssize_t cbDecoded = RTBase64DecodedSizeEx((const char *)pbContent, cbContent, NULL); + if (cbDecoded < 0) + return VERR_INVALID_BASE64_ENCODING; + + *pcbDecoded = cbDecoded; + void *pvDecoded = !fSensitive ? RTMemAlloc(cbDecoded) : RTMemSaferAllocZ(cbDecoded); + if (!pvDecoded) + return VERR_NO_MEMORY; + + size_t cbActual; + int rc = RTBase64DecodeEx((const char *)pbContent, cbContent, pvDecoded, cbDecoded, &cbActual, NULL); + if (RT_SUCCESS(rc)) + { + if (cbActual == (size_t)cbDecoded) + { + *ppvDecoded = pvDecoded; + return VINF_SUCCESS; + } + + rc = VERR_INTERNAL_ERROR_3; + } + if (!fSensitive) + RTMemFree(pvDecoded); + else + RTMemSaferFree(pvDecoded, cbDecoded); + return rc; +} + + +/** + * Checks if the content of a file looks to be binary or not. + * + * @returns true if likely to be binary, false if not binary. + * @param pbFile The file bytes to scan. + * @param cbFile The number of bytes. + * @param fFlags RTCRPEMREADFILE_F_XXX + */ +static bool rtCrPemIsBinaryBlob(uint8_t const *pbFile, size_t cbFile, uint32_t fFlags) +{ + if (fFlags & RTCRPEMREADFILE_F_ONLY_PEM) + return false; + + /* + * Well formed PEM files should probably only contain 7-bit ASCII and + * restrict thenselfs to the following control characters: + * tab, newline, return, form feed + * + * However, if we want to read PEM files which contains human readable + * certificate details before or after each base-64 section, we can't stick + * to 7-bit ASCII. We could say it must be UTF-8, but that's probably to + * limited as well. So, we'll settle for detecting binary files by control + * characters alone (safe enough for DER encoded stuff, I think). + */ + while (cbFile-- > 0) + { + uint8_t const b = *pbFile++; + if (b < 32 && b != '\t' && b != '\n' && b != '\r' && b != '\f') + { + /* Ignore EOT (4), SUB (26) and NUL (0) at the end of the file. */ + if ( (b == 4 || b == 26) + && ( cbFile == 0 + || ( cbFile == 1 + && *pbFile == '\0'))) + return false; + + if (b == 0 && cbFile == 0) + return false; + + return true; + } + } + return false; +} + + +RTDECL(int) RTCrPemFreeSections(PCRTCRPEMSECTION pSectionHead) +{ + while (pSectionHead != NULL) + { + PRTCRPEMSECTION pFree = (PRTCRPEMSECTION)pSectionHead; + pSectionHead = pSectionHead->pNext; + ASMCompilerBarrier(); /* paranoia */ + + if (pFree->pbData) + { + if (!pFree->fSensitive) + RTMemFree(pFree->pbData); + else + RTMemSaferFree(pFree->pbData, pFree->cbData); + pFree->pbData = NULL; + pFree->cbData = 0; + } + + PRTCRPEMFIELD pField = (PRTCRPEMFIELD)pFree->pFieldHead; + if (pField) + { + pFree->pFieldHead = NULL; + do + { + PRTCRPEMFIELD pFreeField = pField; + pField = (PRTCRPEMFIELD)pField->pNext; + ASMCompilerBarrier(); /* paranoia */ + + pFreeField->pszValue = NULL; + RTMemFree(pFreeField); + } while (pField); + } + + RTMemFree(pFree); + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrPemParseContent(void const *pvContent, size_t cbContent, uint32_t fFlags, + PCRTCRPEMMARKER paMarkers, size_t cMarkers, + PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo) +{ + RT_NOREF_PV(pErrInfo); + + /* + * Input validation. + */ + AssertPtr(ppSectionHead); + *ppSectionHead = NULL; + AssertReturn(cbContent, VINF_EOF); + AssertPtr(pvContent); + AssertPtr(paMarkers); + AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS); + + /* + * Pre-allocate a section. + */ + int rc = VINF_SUCCESS; + PRTCRPEMSECTION pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection)); + if (pSection) + { + bool const fSensitive = RT_BOOL(fFlags & RTCRPEMREADFILE_F_SENSITIVE); + + /* + * Try locate the first section. + */ + uint8_t const *pbContent = (uint8_t const *)pvContent; + size_t offBegin, offEnd, offResume; + PCRTCRPEMMARKER pMatch; + if ( !rtCrPemIsBinaryBlob(pbContent, cbContent, fFlags) + && rtCrPemFindMarkerSection(pbContent, cbContent, 0 /*offStart*/, paMarkers, cMarkers, + &pMatch, &offBegin, &offEnd, &offResume) ) + { + PCRTCRPEMSECTION *ppNext = ppSectionHead; + for (;;) + { + //pSection->pNext = NULL; + pSection->pMarker = pMatch; + //pSection->pbData = NULL; + //pSection->cbData = 0; + //pSection->pFieldHead = NULL; + pSection->fSensitive = fSensitive; + + *ppNext = pSection; + ppNext = &pSection->pNext; + + /* + * Decode the section. + */ + size_t cbFields = 0; + int rc2 = rtCrPemProcessFields(pSection, pbContent + offBegin, offEnd - offBegin, &cbFields); + offBegin += cbFields; + if (RT_SUCCESS(rc2)) + rc2 = rtCrPemDecodeBase64(pbContent + offBegin, offEnd - offBegin, fSensitive, + (void **)&pSection->pbData, &pSection->cbData); + if (RT_FAILURE(rc2)) + { + pSection->pbData = NULL; + pSection->cbData = 0; + if ( rc2 == VERR_INVALID_BASE64_ENCODING + && (fFlags & RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR)) + rc = -rc2; + else + { + rc = rc2; + break; + } + } + + /* + * More sections? + */ + if ( offResume + 12 >= cbContent + || offResume >= cbContent + || !rtCrPemFindMarkerSection(pbContent, cbContent, offResume, paMarkers, cMarkers, + &pMatch, &offBegin, &offEnd, &offResume) ) + break; /* No. */ + + /* Ok, allocate a new record for it. */ + pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection)); + if (RT_UNLIKELY(!pSection)) + { + rc = VERR_NO_MEMORY; + break; + } + } + if (RT_SUCCESS(rc)) + return rc; + + RTCrPemFreeSections(*ppSectionHead); + } + else + { + if (!(fFlags & RTCRPEMREADFILE_F_ONLY_PEM)) + { + /* + * No PEM section found. Return the whole file as one binary section. + */ + //pSection->pNext = NULL; + //pSection->pMarker = NULL; + //pSection->pFieldHead = NULL; + pSection->cbData = cbContent; + pSection->fSensitive = fSensitive; + if (!fSensitive) + pSection->pbData = (uint8_t *)RTMemDup(pbContent, cbContent); + else + { + pSection->pbData = (uint8_t *)RTMemSaferAllocZ(cbContent); + if (pSection->pbData) + memcpy(pSection->pbData, pbContent, cbContent); + } + if (pSection->pbData) + { + *ppSectionHead = pSection; + return VINF_SUCCESS; + } + + rc = VERR_NO_MEMORY; + } + else + rc = VWRN_NOT_FOUND; + RTMemFree(pSection); + } + } + else + rc = VERR_NO_MEMORY; + *ppSectionHead = NULL; + return rc; +} + + +RTDECL(int) RTCrPemReadFile(const char *pszFilename, uint32_t fFlags, PCRTCRPEMMARKER paMarkers, size_t cMarkers, + PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo) +{ + *ppSectionHead = NULL; + AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS); + + size_t cbContent; + void *pvContent; + int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent); + if (RT_SUCCESS(rc)) + { + rc = RTCrPemParseContent(pvContent, cbContent, fFlags, paMarkers, cMarkers, ppSectionHead, pErrInfo); + if (fFlags & RTCRPEMREADFILE_F_SENSITIVE) + RTMemWipeThoroughly(pvContent, cbContent, 3); + RTFileReadAllFree(pvContent, cbContent); + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename); + return rc; +} + + +RTDECL(const char *) RTCrPemFindFirstSectionInContent(void const *pvContent, size_t cbContent, + PCRTCRPEMMARKER paMarkers, size_t cMarkers) +{ + size_t offBegin; + if (rtCrPemFindMarker((uint8_t *)pvContent, cbContent, 0, "BEGIN", 5, paMarkers, cMarkers, NULL, &offBegin, NULL)) + return (const char *)pvContent + offBegin; + return NULL; +} |