diff options
Diffstat (limited to 'src/VBox/Runtime/common/asn1')
40 files changed, 11656 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/asn1/Makefile.kup b/src/VBox/Runtime/common/asn1/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/common/asn1/Makefile.kup diff --git a/src/VBox/Runtime/common/asn1/asn1-basics.cpp b/src/VBox/Runtime/common/asn1/asn1-basics.cpp new file mode 100644 index 00000000..05c73f1a --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-basics.cpp @@ -0,0 +1,611 @@ +/* $Id: asn1-basics.cpp $ */ +/** @file + * IPRT - ASN.1, Basic Operations. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/alloca.h> +#include <iprt/bignum.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * ASN.1 content/value allocation. + * + * The currently most frequent use of the RTAsn1 module is to decode ASN.1 byte + * streams. In that scenario we do not allocate memory for the raw content + * bytes, but share it with the byte stream. Also, a great number of RTASN1CORE + * structures will never need to have any content bytes allocated with this. + * + * So, in order to avoid adding an extra 16 (64-bit) or 8 (32-bit) bytes to each + * RTASN1CORE structure just to keep track of the occational content allocation, + * we put the allocator tracking structure inside the allocation. During + * allocator operations it lives temporarily on the stack. + */ +typedef struct RTASN1MEMCONTENT +{ + /** The allocation tracker. */ + RTASN1ALLOCATION Allocation; +#if ARCH_BITS == 32 + uint32_t Padding; /**< Alignment padding. */ +#endif + /** The content bytes, i.e. what RTASN1CORE::uData.pv points to. Use a 64-bit + * type here to emphasize that it's 8-byte aligned on all platforms. */ + uint64_t au64Content[1]; +} RTASN1MEMCONTENT; +AssertCompileMemberAlignment(RTASN1MEMCONTENT, au64Content, 8); +/** Pointer to a ASN.1 content allocation. */ +typedef RTASN1MEMCONTENT *PRTASN1MEMCONTENT; + + + +RTDECL(int) RTAsn1MemResizeArray(PRTASN1ARRAYALLOCATION pAllocation, void ***ppapvArray, uint32_t cCurrent, uint32_t cNew) +{ + AssertReturn(pAllocation->pAllocator != NULL, VERR_WRONG_ORDER); + AssertReturn(pAllocation->cbEntry > 0, VERR_WRONG_ORDER); + AssertReturn(cCurrent <= pAllocation->cEntriesAllocated, VERR_INVALID_PARAMETER); + AssertReturn(cCurrent <= pAllocation->cPointersAllocated, VERR_INVALID_PARAMETER); + AssertReturn(cNew < _1M, VERR_OUT_OF_RANGE); + Assert(pAllocation->cEntriesAllocated <= pAllocation->cPointersAllocated); + + /* + * Is there sufficent space allocated already? + * + * We keep unused entires ZEROed, therefore we must always call the allocator + * when shrinking (this also helps with the electric fence allocator). + */ + if (cNew <= pAllocation->cEntriesAllocated) + { + if (cCurrent <= cNew) + return VINF_SUCCESS; + pAllocation->pAllocator->pfnShrinkArray(pAllocation->pAllocator, pAllocation, ppapvArray, cCurrent, cNew); + return VINF_SUCCESS; + } + + /* + * Must grow (or do initial alloc). + */ + pAllocation->cResizeCalls++; + return pAllocation->pAllocator->pfnGrowArray(pAllocation->pAllocator, pAllocation, ppapvArray, cNew); +} + + +RTDECL(void) RTAsn1MemFreeArray(PRTASN1ARRAYALLOCATION pAllocation, void **papvArray) +{ + Assert(pAllocation->pAllocator != NULL); + if (papvArray) + { + pAllocation->pAllocator->pfnFreeArray(pAllocation->pAllocator, pAllocation, papvArray); + Assert(pAllocation->cPointersAllocated == 0); + Assert(pAllocation->cEntriesAllocated == 0); + } +} + + +RTDECL(int) RTAsn1MemAllocZ(PRTASN1ALLOCATION pAllocation, void **ppvMem, size_t cbMem) +{ + AssertReturn(pAllocation->pAllocator != NULL, VERR_WRONG_ORDER); + AssertPtr(ppvMem); + Assert(cbMem > 0); + int rc = pAllocation->pAllocator->pfnAlloc(pAllocation->pAllocator, pAllocation, ppvMem, cbMem); + Assert(pAllocation->cbAllocated >= cbMem || RT_FAILURE_NP(rc)); + return rc; +} + + +RTDECL(int) RTAsn1MemDup(PRTASN1ALLOCATION pAllocation, void **ppvMem, const void *pvSrc, size_t cbMem) +{ + AssertReturn(pAllocation->pAllocator != NULL, VERR_WRONG_ORDER); + AssertPtr(ppvMem); + AssertPtr(pvSrc); + Assert(cbMem > 0); + int rc = pAllocation->pAllocator->pfnAlloc(pAllocation->pAllocator, pAllocation, ppvMem, cbMem); + if (RT_SUCCESS(rc)) + { + Assert(pAllocation->cbAllocated >= cbMem); + memcpy(*ppvMem, pvSrc, cbMem); + return VINF_SUCCESS; + } + return rc; +} + + +RTDECL(void) RTAsn1MemFree(PRTASN1ALLOCATION pAllocation, void *pv) +{ + Assert(pAllocation->pAllocator != NULL); + if (pv) + { + pAllocation->pAllocator->pfnFree(pAllocation->pAllocator, pAllocation, pv); + Assert(pAllocation->cbAllocated == 0); + } +} + + +RTDECL(PRTASN1ALLOCATION) RTAsn1MemInitAllocation(PRTASN1ALLOCATION pAllocation, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + pAllocation->cbAllocated = 0; + pAllocation->cReallocs = 0; + pAllocation->uReserved0 = 0; + pAllocation->pAllocator = pAllocator; + return pAllocation; +} + + +RTDECL(PRTASN1ARRAYALLOCATION) RTAsn1MemInitArrayAllocation(PRTASN1ARRAYALLOCATION pAllocation, + PCRTASN1ALLOCATORVTABLE pAllocator, size_t cbEntry) +{ + Assert(cbEntry >= sizeof(RTASN1CORE)); + Assert(cbEntry < _1M); + Assert(RT_ALIGN_Z(cbEntry, sizeof(void *)) == cbEntry); + pAllocation->cbEntry = (uint32_t)cbEntry; + pAllocation->cPointersAllocated = 0; + pAllocation->cEntriesAllocated = 0; + pAllocation->cResizeCalls = 0; + pAllocation->uReserved0 = 0; + pAllocation->pAllocator = pAllocator; + return pAllocation; +} + + +RTDECL(int) RTAsn1ContentAllocZ(PRTASN1CORE pAsn1Core, size_t cb, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertReturn(pAllocator != NULL, VERR_WRONG_ORDER); + AssertReturn(cb > 0 && cb < _1G, VERR_INVALID_PARAMETER); + AssertPtr(pAsn1Core); + AssertReturn(!(pAsn1Core->fFlags & RTASN1CORE_F_ALLOCATED_CONTENT), VERR_INVALID_STATE); + + /* Initialize the temporary allocation tracker. */ + RTASN1ALLOCATION Allocation; + Allocation.cbAllocated = 0; + Allocation.cReallocs = 0; + Allocation.uReserved0 = 0; + Allocation.pAllocator = pAllocator; + + /* Make the allocation. */ + uint32_t cbAlloc = RT_UOFFSETOF(RTASN1MEMCONTENT, au64Content) + (uint32_t)cb; + PRTASN1MEMCONTENT pHdr; + int rc = pAllocator->pfnAlloc(pAllocator, &Allocation, (void **)&pHdr, cbAlloc); + if (RT_SUCCESS(rc)) + { + Assert(Allocation.cbAllocated >= cbAlloc); + pHdr->Allocation = Allocation; + pAsn1Core->cb = (uint32_t)cb; + pAsn1Core->uData.pv = &pHdr->au64Content[0]; + pAsn1Core->fFlags |= RTASN1CORE_F_ALLOCATED_CONTENT; + } + + return rc; +} + + +RTDECL(int) RTAsn1ContentDup(PRTASN1CORE pAsn1Core, void const *pvSrc, size_t cbSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + int rc = RTAsn1ContentAllocZ(pAsn1Core, cbSrc, pAllocator); + if (RT_SUCCESS(rc)) + memcpy((void *)pAsn1Core->uData.pv, pvSrc, cbSrc); + return rc; +} + + +RTDECL(int) RTAsn1ContentReallocZ(PRTASN1CORE pAsn1Core, size_t cb, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + /* Validate input. */ + AssertPtr(pAsn1Core); + AssertReturn(cb < _1G, VERR_INVALID_PARAMETER); + + if (cb > 0) + { + /* + * Case 1 - Initial allocation. + */ + uint32_t cbNeeded = RT_UOFFSETOF(RTASN1MEMCONTENT, au64Content) + (uint32_t)cb; + if (!(pAsn1Core->fFlags & RTASN1CORE_F_ALLOCATED_CONTENT)) + return RTAsn1ContentAllocZ(pAsn1Core, cb, pAllocator); + + /* Locate the header. */ + PRTASN1MEMCONTENT pHdr = RT_FROM_MEMBER(pAsn1Core->uData.pv, RTASN1MEMCONTENT, au64Content); + + /* + * Case 2 - Reallocation using the same allocator. + */ + if ( pHdr->Allocation.pAllocator == pAllocator + || !pAllocator) + { + pHdr->Allocation.cReallocs++; + + /* Modify the allocation if necessary. */ + if (pHdr->Allocation.cbAllocated < cbNeeded) + { + RTASN1ALLOCATION Allocation = pHdr->Allocation; + int rc = Allocation.pAllocator->pfnRealloc(Allocation.pAllocator, &Allocation, pHdr, (void **)&pHdr, cbNeeded); + if (RT_FAILURE(rc)) + return rc; + Assert(Allocation.cbAllocated >= cbNeeded); + pAsn1Core->uData.pv = &pHdr->au64Content[0]; + pHdr->Allocation = Allocation; + } + + /* Clear any additional memory we're letting the user use and + update the content size. */ + if (pAsn1Core->cb < cb) + RT_BZERO((uint8_t *)&pAsn1Core->uData.pu8[pAsn1Core->cb], cb - pAsn1Core->cb); + pAsn1Core->cb = (uint32_t)cb; + } + /* + * Case 3 - Reallocation using a different allocator. + */ + else + { + /* Initialize the temporary allocation tracker. */ + RTASN1ALLOCATION Allocation; + Allocation.cbAllocated = 0; + Allocation.cReallocs = pHdr->Allocation.cReallocs + 1; + Allocation.uReserved0 = 0; + Allocation.pAllocator = pAllocator; + + /* Make the allocation. */ + PRTASN1MEMCONTENT pHdrNew; + int rc = pAllocator->pfnAlloc(pAllocator, &Allocation, (void **)&pHdrNew, cbNeeded); + if (RT_FAILURE(rc)) + return rc; + Assert(Allocation.cbAllocated >= cbNeeded); + + /* Duplicate the old content and zero any new memory we might've added. */ + if (pAsn1Core->cb >= cb) + memcpy(&pHdrNew->au64Content[0], &pHdr->au64Content[0], cb); + else + { + memcpy(&pHdrNew->au64Content[0], &pHdr->au64Content[0], pAsn1Core->cb); + RT_BZERO((uint8_t *)&pHdrNew->au64Content[0] + pAsn1Core->cb, cb - pAsn1Core->cb); + } + + /* Update the core. */ + pHdrNew->Allocation = Allocation; + pAsn1Core->uData.pv = &pHdrNew->au64Content[0]; + pAsn1Core->fFlags |= RTASN1CORE_F_ALLOCATED_CONTENT; /* free cleared it. */ + pAsn1Core->cb = (uint32_t)cb; + + /* Free the old content. */ + Allocation = pHdr->Allocation; + Allocation.pAllocator->pfnFree(Allocation.pAllocator, &Allocation, pHdr); + Assert(Allocation.cbAllocated == 0); + } + } + /* + * Case 4 - It's a request to free the memory. + */ + else + RTAsn1ContentFree(pAsn1Core); + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1ContentFree(PRTASN1CORE pAsn1Core) +{ + if (pAsn1Core) + { + pAsn1Core->cb = 0; + if (pAsn1Core->fFlags & RTASN1CORE_F_ALLOCATED_CONTENT) + { + pAsn1Core->fFlags &= ~RTASN1CORE_F_ALLOCATED_CONTENT; + AssertReturnVoid(pAsn1Core->uData.pv); + + PRTASN1MEMCONTENT pHdr = RT_FROM_MEMBER(pAsn1Core->uData.pv, RTASN1MEMCONTENT, au64Content); + RTASN1ALLOCATION Allocation = pHdr->Allocation; + + Allocation.pAllocator->pfnFree(Allocation.pAllocator, &Allocation, pHdr); + Assert(Allocation.cbAllocated == 0); + } + pAsn1Core->uData.pv = NULL; + } +} + + + +/* + * Virtual method table based API. + */ + +RTDECL(void) RTAsn1VtDelete(PRTASN1CORE pThisCore) +{ + if (pThisCore) + { + PCRTASN1COREVTABLE pOps = pThisCore->pOps; + if (pOps) + pOps->pfnDtor(pThisCore); + } +} + + +/** + * Context data passed by RTAsn1VtDeepEnum to it's worker callbacks. + */ +typedef struct RTASN1DEEPENUMCTX +{ + PFNRTASN1ENUMCALLBACK pfnCallback; + void *pvUser; +} RTASN1DEEPENUMCTX; + + +static DECLCALLBACK(int) rtAsn1VtDeepEnumDepthFirst(PRTASN1CORE pThisCore, const char *pszName, uint32_t uDepth, void *pvUser) +{ + AssertReturn(pThisCore, VINF_SUCCESS); + + if (pThisCore->pOps && pThisCore->pOps->pfnEnum) + { + int rc = pThisCore->pOps->pfnEnum(pThisCore, rtAsn1VtDeepEnumDepthFirst, uDepth, pvUser); + if (rc != VINF_SUCCESS) + return rc; + } + + RTASN1DEEPENUMCTX *pCtx = (RTASN1DEEPENUMCTX *)pvUser; + return pCtx->pfnCallback(pThisCore, pszName, uDepth, pCtx->pvUser); +} + + +static DECLCALLBACK(int) rtAsn1VtDeepEnumDepthLast(PRTASN1CORE pThisCore, const char *pszName, uint32_t uDepth, void *pvUser) +{ + AssertReturn(pThisCore, VINF_SUCCESS); + + RTASN1DEEPENUMCTX *pCtx = (RTASN1DEEPENUMCTX *)pvUser; + int rc = pCtx->pfnCallback(pThisCore, pszName, uDepth, pCtx->pvUser); + if (rc == VINF_SUCCESS) + { + if (pThisCore->pOps && pThisCore->pOps->pfnEnum) + rc = pThisCore->pOps->pfnEnum(pThisCore, rtAsn1VtDeepEnumDepthFirst, uDepth, pvUser); + } + return rc; +} + + +RTDECL(int) RTAsn1VtDeepEnum(PRTASN1CORE pThisCore, bool fDepthFirst, uint32_t uDepth, + PFNRTASN1ENUMCALLBACK pfnCallback, void *pvUser) +{ + int rc; + if (RTAsn1Core_IsPresent(pThisCore)) + { + PCRTASN1COREVTABLE pOps = pThisCore->pOps; + if (pOps && pOps->pfnEnum) + { + RTASN1DEEPENUMCTX Ctx; + Ctx.pfnCallback = pfnCallback; + Ctx.pvUser = pvUser; + rc = pOps->pfnEnum(pThisCore, fDepthFirst ? rtAsn1VtDeepEnumDepthFirst : rtAsn1VtDeepEnumDepthLast, uDepth, &Ctx); + } + else + rc = VINF_SUCCESS; + } + else + rc = VINF_SUCCESS; + return rc; +} + + +RTDECL(int) RTAsn1VtClone(PRTASN1CORE pThisCore, PRTASN1CORE pSrcCore, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtrReturn(pThisCore, VERR_INVALID_POINTER); + AssertPtrReturn(pSrcCore, VERR_INVALID_POINTER); + AssertPtrReturn(pAllocator, VERR_INVALID_POINTER); + + if (RTAsn1Core_IsPresent(pSrcCore)) + { + AssertPtrReturn(pSrcCore->pOps, VERR_INVALID_POINTER); + AssertPtr(pSrcCore->pOps->pfnClone); + return pSrcCore->pOps->pfnClone(pThisCore, pSrcCore, pAllocator); + } + + RT_ZERO(*pThisCore); + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1VtCompare(PCRTASN1CORE pLeftCore, PCRTASN1CORE pRightCore) +{ + int iDiff; + if (RTAsn1Core_IsPresent(pLeftCore)) + { + if (RTAsn1Core_IsPresent(pRightCore)) + { + PCRTASN1COREVTABLE pOps = pLeftCore->pOps; + if (pOps == pRightCore->pOps) + { + AssertPtr(pOps->pfnCompare); + iDiff = pOps->pfnCompare(pLeftCore, pRightCore); + } + else + iDiff = (uintptr_t)pOps < (uintptr_t)pRightCore->pOps ? -1 : 1; + } + else + iDiff = 1; + } + else + iDiff = 0 - (int)RTAsn1Core_IsPresent(pRightCore); + return iDiff; +} + + +RTDECL(int) RTAsn1VtCheckSanity(PCRTASN1CORE pThisCore, uint32_t fFlags, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + int rc; + if (RTAsn1Core_IsPresent(pThisCore)) + { + PCRTASN1COREVTABLE pOps = pThisCore->pOps; + if (pOps && pOps->pfnCheckSanity) + rc = pOps->pfnCheckSanity(pThisCore, fFlags, pErrInfo, pszErrorTag); + else if (pOps) + rc = RTErrInfoSetF(pErrInfo, VERR_ASN1_NO_CHECK_SANITY_METHOD, + "%s: Has no pfnCheckSanity function.", pszErrorTag); + else + rc = RTErrInfoSetF(pErrInfo, VERR_ASN1_NO_VTABLE, "%s: Has no Vtable function.", pszErrorTag); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Not present.", pszErrorTag); + return rc; +} + + + +/* + * Dummy ASN.1 object. + */ + +RTDECL(int) RTAsn1Dummy_InitEx(PRTASN1DUMMY pThis) +{ + return RTAsn1Core_InitEx(&pThis->Asn1Core, + UINT32_MAX, + ASN1_TAGCLASS_PRIVATE | ASN1_TAGFLAG_CONSTRUCTED, + NULL, + RTASN1CORE_F_DUMMY); +} + + +/* + * ASN.1 SEQUENCE OF object. + */ + +RTDECL(int) RTAsn1SeqOfCore_Init(PRTASN1SEQOFCORE pThis, PCRTASN1COREVTABLE pVtable) +{ + return RTAsn1Core_InitEx(&pThis->Asn1Core, + ASN1_TAG_SEQUENCE, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED, + pVtable, + RTASN1CORE_F_PRESENT); +} + + +RTDECL(int) RTAsn1SeqOfCore_Clone(PRTASN1SEQOFCORE pThis, PCRTASN1COREVTABLE pVtable, PCRTASN1SEQOFCORE pSrc) +{ + AssertReturn(pSrc->Asn1Core.pOps == pVtable, VERR_ASN1_INTERNAL_ERROR_5); + return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); +} + + +/* + * ASN.1 SET OF object. + */ + +RTDECL(int) RTAsn1SetOfCore_Init(PRTASN1SETOFCORE pThis, PCRTASN1COREVTABLE pVtable) +{ + return RTAsn1Core_InitEx(&pThis->Asn1Core, + ASN1_TAG_SET, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED, + pVtable, + RTASN1CORE_F_PRESENT); +} + + +RTDECL(int) RTAsn1SetOfCore_Clone(PRTASN1SETOFCORE pThis, PCRTASN1COREVTABLE pVtable, PCRTASN1SETOFCORE pSrc) +{ + AssertReturn(pSrc->Asn1Core.pOps == pVtable, VERR_ASN1_INTERNAL_ERROR_5); + return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); +} + + +/* + * ASN.1 SEQUENCE object. + */ + +RTDECL(int) RTAsn1SequenceCore_Init(PRTASN1SEQUENCECORE pThis, PCRTASN1COREVTABLE pVtable) +{ + return RTAsn1Core_InitEx(&pThis->Asn1Core, + ASN1_TAG_SEQUENCE, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED, + pVtable, + RTASN1CORE_F_PRESENT); +} + + +RTDECL(int) RTAsn1SequenceCore_Clone(PRTASN1SEQUENCECORE pThis, PCRTASN1COREVTABLE pVtable, PCRTASN1SEQUENCECORE pSrc) +{ + AssertReturn(pSrc->Asn1Core.pOps == pVtable, VERR_ASN1_INTERNAL_ERROR_5); + return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); +} + + +/* + * ASN.1 SEQUENCE object - only used by SPC, so probably doing something wrong there. + */ + +RTDECL(int) RTAsn1SetCore_Init(PRTASN1SETCORE pThis, PCRTASN1COREVTABLE pVtable) +{ + return RTAsn1Core_InitEx(&pThis->Asn1Core, + ASN1_TAG_SET, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED, + pVtable, + RTASN1CORE_F_PRESENT); +} + + +RTDECL(int) RTAsn1SetCore_Clone(PRTASN1SETCORE pThis, PCRTASN1COREVTABLE pVtable, PCRTASN1SETCORE pSrc) +{ + AssertReturn(pSrc->Asn1Core.pOps == pVtable, VERR_ASN1_INTERNAL_ERROR_5); + return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); +} + + +/* + * ASN.1 Context Tag object. + */ + +RTDECL(int) RTAsn1ContextTagN_Init(PRTASN1CONTEXTTAG pThis, uint32_t uTag, PCRTASN1COREVTABLE pVtable) +{ + return RTAsn1Core_InitEx(&pThis->Asn1Core, + uTag, + ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_CONSTRUCTED, + pVtable, + RTASN1CORE_F_PRESENT); +} + + +RTDECL(int) RTAsn1ContextTagN_Clone(PRTASN1CONTEXTTAG pThis, PCRTASN1CONTEXTTAG pSrc, uint32_t uTag) +{ + Assert(pSrc->Asn1Core.uTag == uTag || !RTASN1CORE_IS_PRESENT(&pSrc->Asn1Core)); RT_NOREF_PV(uTag); + return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); +} + diff --git a/src/VBox/Runtime/common/asn1/asn1-cursor.cpp b/src/VBox/Runtime/common/asn1/asn1-cursor.cpp new file mode 100644 index 00000000..3150e67c --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-cursor.cpp @@ -0,0 +1,678 @@ +/* $Id: asn1-cursor.cpp $ */ +/** @file + * IPRT - ASN.1, Basic Operations. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/asm.h> +#include <iprt/alloca.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/ctype.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def RTASN1_MAX_NESTING + * The maximum nesting depth we allow. This limit is enforced to avoid running + * out of stack due to malformed ASN.1 input. + * + * For reference, 'RTSignTool verify-exe RTSignTool.exe', requires a value of 15 + * to work without hitting the limit for signatures with simple timestamps, and + * 23 (amd64/rel = ~3KB) for the new microsoft timestamp counter signatures. + */ +#ifdef IN_RING3 +# define RTASN1_MAX_NESTING 64 +#else +# define RTASN1_MAX_NESTING 32 +#endif + + + +RTDECL(PRTASN1CURSOR) RTAsn1CursorInitPrimary(PRTASN1CURSORPRIMARY pPrimaryCursor, void const *pvFirst, uint32_t cb, + PRTERRINFO pErrInfo, PCRTASN1ALLOCATORVTABLE pAllocator, uint32_t fFlags, + const char *pszErrorTag) +{ + pPrimaryCursor->Cursor.pbCur = (uint8_t const *)pvFirst; + pPrimaryCursor->Cursor.cbLeft = cb; + pPrimaryCursor->Cursor.fFlags = (uint8_t)fFlags; Assert(fFlags <= UINT8_MAX); + pPrimaryCursor->Cursor.cDepth = 0; + pPrimaryCursor->Cursor.abReserved[0] = 0; + pPrimaryCursor->Cursor.abReserved[1] = 0; + pPrimaryCursor->Cursor.pPrimary = pPrimaryCursor; + pPrimaryCursor->Cursor.pUp = NULL; + pPrimaryCursor->Cursor.pszErrorTag = pszErrorTag; + pPrimaryCursor->pErrInfo = pErrInfo; + pPrimaryCursor->pAllocator = pAllocator; + pPrimaryCursor->pbFirst = (uint8_t const *)pvFirst; + return &pPrimaryCursor->Cursor; +} + + +RTDECL(int) RTAsn1CursorInitSub(PRTASN1CURSOR pParent, uint32_t cb, PRTASN1CURSOR pChild, const char *pszErrorTag) +{ + AssertReturn(pParent->pPrimary, VERR_ASN1_INTERNAL_ERROR_1); + AssertReturn(pParent->pbCur, VERR_ASN1_INTERNAL_ERROR_2); + + pChild->pbCur = pParent->pbCur; + pChild->cbLeft = cb; + pChild->fFlags = pParent->fFlags & ~RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH; + pChild->cDepth = pParent->cDepth + 1; + AssertReturn(pChild->cDepth < RTASN1_MAX_NESTING, VERR_ASN1_TOO_DEEPLY_NESTED); + pChild->abReserved[0] = 0; + pChild->abReserved[1] = 0; + pChild->pPrimary = pParent->pPrimary; + pChild->pUp = pParent; + pChild->pszErrorTag = pszErrorTag; + + AssertReturn(pParent->cbLeft >= cb, VERR_ASN1_INTERNAL_ERROR_3); + pParent->pbCur += cb; + pParent->cbLeft -= cb; + + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1CursorInitSubFromCore(PRTASN1CURSOR pParent, PRTASN1CORE pAsn1Core, + PRTASN1CURSOR pChild, const char *pszErrorTag) +{ + AssertReturn(pParent->pPrimary, VERR_ASN1_INTERNAL_ERROR_1); + AssertReturn(pParent->pbCur, VERR_ASN1_INTERNAL_ERROR_2); + + pChild->pbCur = pAsn1Core->uData.pu8; + pChild->cbLeft = pAsn1Core->cb; + pChild->fFlags = pParent->fFlags & ~RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH; + pChild->cDepth = pParent->cDepth + 1; + AssertReturn(pChild->cDepth < RTASN1_MAX_NESTING, VERR_ASN1_TOO_DEEPLY_NESTED); + pChild->abReserved[0] = 0; + pChild->abReserved[1] = 0; + pChild->pPrimary = pParent->pPrimary; + pChild->pUp = pParent; + pChild->pszErrorTag = pszErrorTag; + + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1CursorSetInfoV(PRTASN1CURSOR pCursor, int rc, const char *pszMsg, va_list va) +{ + PRTERRINFO pErrInfo = pCursor->pPrimary->pErrInfo; + if (pErrInfo) + { + /* Format the message. */ + RTErrInfoSetV(pErrInfo, rc, pszMsg, va); + + /* Add the prefixes. This isn't the fastest way, but it's the one + which eats the least stack. */ + char *pszBuf = pErrInfo->pszMsg; + size_t cbBuf = pErrInfo->cbMsg; + if (pszBuf && cbBuf > 32) + { + size_t cbMove = strlen(pszBuf) + 1; + + /* Make sure there is a ': '. */ + bool fFirst = false; + if (pszMsg[0] != '%' || pszMsg[1] != 's' || pszMsg[2] != ':') + { + if (cbMove + 2 < cbBuf) + { + memmove(pszBuf + 2, pszBuf, cbMove); + pszBuf[0] = ':'; + pszBuf[1] = ' '; + cbMove += 2; + fFirst = true; + } + } + + /* Add the prefixes from the cursor chain. */ + while (pCursor) + { + if (pCursor->pszErrorTag) + { + size_t cchErrorTag = strlen(pCursor->pszErrorTag); + if (cchErrorTag + !fFirst + cbMove > cbBuf) + break; + memmove(pszBuf + cchErrorTag + !fFirst, pszBuf, cbMove); + memcpy(pszBuf, pCursor->pszErrorTag, cchErrorTag); + if (!fFirst) + pszBuf[cchErrorTag] = '.'; + cbMove += cchErrorTag + !fFirst; + fFirst = false; + } + pCursor = pCursor->pUp; + } + } + } + + return rc; +} + + +RTDECL(int) RTAsn1CursorSetInfo(PRTASN1CURSOR pCursor, int rc, const char *pszMsg, ...) +{ + va_list va; + va_start(va, pszMsg); + rc = RTAsn1CursorSetInfoV(pCursor, rc, pszMsg, va); + va_end(va); + return rc; +} + + +RTDECL(bool) RTAsn1CursorIsEnd(PRTASN1CURSOR pCursor) +{ + if (pCursor->cbLeft == 0) + return true; + if (!(pCursor->fFlags & RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH)) + return false; + return pCursor->cbLeft >= 2 + && pCursor->pbCur[0] == 0 + && pCursor->pbCur[1] == 0; +} + + +RTDECL(int) RTAsn1CursorCheckEnd(PRTASN1CURSOR pCursor) +{ + if (!(pCursor->fFlags & RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH)) + { + if (pCursor->cbLeft == 0) + return VINF_SUCCESS; + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END, + "%u (%#x) bytes left over", pCursor->cbLeft, pCursor->cbLeft); + } + + /* + * There must be exactly two zero bytes here. + */ + if (pCursor->cbLeft == 2) + { + if ( pCursor->pbCur[0] == 0 + && pCursor->pbCur[1] == 0) + return VINF_SUCCESS; + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END, + "%u (%#x) bytes left over [indef: %.*Rhxs]", + pCursor->cbLeft, pCursor->cbLeft, RT_MIN(pCursor->cbLeft, 16), pCursor->pbCur); + } + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END, + "%u (%#x) byte(s) left over, exepcted exactly two zero bytes [indef len]", + pCursor->cbLeft, pCursor->cbLeft); +} + + +/** + * Worker for RTAsn1CursorCheckSeqEnd and RTAsn1CursorCheckSetEnd. + */ +static int rtAsn1CursorCheckSeqOrSetEnd(PRTASN1CURSOR pCursor, PRTASN1CORE pAsn1Core) +{ + if (!(pAsn1Core->fFlags & RTASN1CORE_F_INDEFINITE_LENGTH)) + { + if (pCursor->cbLeft == 0) + return VINF_SUCCESS; + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END, + "%u (%#x) bytes left over", pCursor->cbLeft, pCursor->cbLeft); + } + + if (pCursor->cbLeft >= 2) + { + if ( pCursor->pbCur[0] == 0 + && pCursor->pbCur[1] == 0) + { + pAsn1Core->cb = (uint32_t)(pCursor->pbCur - pAsn1Core->uData.pu8); + pCursor->cbLeft -= 2; + pCursor->pbCur += 2; + + PRTASN1CURSOR pParentCursor = pCursor->pUp; + if ( pParentCursor + && (pParentCursor->fFlags & RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH)) + { + pParentCursor->pbCur -= pCursor->cbLeft; + pParentCursor->cbLeft += pCursor->cbLeft; + return VINF_SUCCESS; + } + + if (pCursor->cbLeft == 0) + return VINF_SUCCESS; + + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END, + "%u (%#x) bytes left over (parent not indefinite length)", pCursor->cbLeft, pCursor->cbLeft); + } + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END, "%u (%#x) bytes left over [indef: %.*Rhxs]", + pCursor->cbLeft, pCursor->cbLeft, RT_MIN(pCursor->cbLeft, 16), pCursor->pbCur); + } + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END, + "1 byte left over, expected two for indefinite length end-of-content sequence"); +} + + +RTDECL(int) RTAsn1CursorCheckSeqEnd(PRTASN1CURSOR pCursor, PRTASN1SEQUENCECORE pSeqCore) +{ + return rtAsn1CursorCheckSeqOrSetEnd(pCursor, &pSeqCore->Asn1Core); +} + + +RTDECL(int) RTAsn1CursorCheckSetEnd(PRTASN1CURSOR pCursor, PRTASN1SETCORE pSetCore) +{ + return rtAsn1CursorCheckSeqOrSetEnd(pCursor, &pSetCore->Asn1Core); +} + + +RTDECL(int) RTAsn1CursorCheckOctStrEnd(PRTASN1CURSOR pCursor, PRTASN1OCTETSTRING pOctetString) +{ + return rtAsn1CursorCheckSeqOrSetEnd(pCursor, &pOctetString->Asn1Core); +} + + +RTDECL(PRTASN1ALLOCATION) RTAsn1CursorInitAllocation(PRTASN1CURSOR pCursor, PRTASN1ALLOCATION pAllocation) +{ + pAllocation->cbAllocated = 0; + pAllocation->cReallocs = 0; + pAllocation->uReserved0 = 0; + pAllocation->pAllocator = pCursor->pPrimary->pAllocator; + return pAllocation; +} + + +RTDECL(PRTASN1ARRAYALLOCATION) RTAsn1CursorInitArrayAllocation(PRTASN1CURSOR pCursor, PRTASN1ARRAYALLOCATION pAllocation, + size_t cbEntry) +{ + Assert(cbEntry >= sizeof(RTASN1CORE)); + Assert(cbEntry < _1M); + Assert(RT_ALIGN_Z(cbEntry, sizeof(void *)) == cbEntry); + pAllocation->cbEntry = (uint32_t)cbEntry; + pAllocation->cPointersAllocated = 0; + pAllocation->cEntriesAllocated = 0; + pAllocation->cResizeCalls = 0; + pAllocation->uReserved0 = 0; + pAllocation->pAllocator = pCursor->pPrimary->pAllocator; + return pAllocation; +} + + +RTDECL(int) RTAsn1CursorReadHdr(PRTASN1CURSOR pCursor, PRTASN1CORE pAsn1Core, const char *pszErrorTag) +{ + /* + * Initialize the return structure in case of failure. + */ + pAsn1Core->uTag = 0; + pAsn1Core->fClass = 0; + pAsn1Core->uRealTag = 0; + pAsn1Core->fRealClass = 0; + pAsn1Core->cbHdr = 0; + pAsn1Core->cb = 0; + pAsn1Core->fFlags = 0; + pAsn1Core->uData.pv = NULL; + pAsn1Core->pOps = NULL; + + /* + * The header has at least two bytes: Type & length. + */ + if (pCursor->cbLeft >= 2) + { + uint32_t uTag = pCursor->pbCur[0]; + uint32_t cb = pCursor->pbCur[1]; + pCursor->cbLeft -= 2; + pCursor->pbCur += 2; + + pAsn1Core->uRealTag = pAsn1Core->uTag = uTag & ASN1_TAG_MASK; + pAsn1Core->fRealClass = pAsn1Core->fClass = uTag & ~ASN1_TAG_MASK; + pAsn1Core->cbHdr = 2; + if ((uTag & ASN1_TAG_MASK) == ASN1_TAG_USE_LONG_FORM) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_LONG_TAG, + "%s: Implement parsing of tags > 30: %#x (length=%#x)", pszErrorTag, uTag, cb); + + /* Extended length field? */ + if (cb & RT_BIT(7)) + { + if (cb != RT_BIT(7)) + { + /* Definite form. */ + uint8_t cbEnc = cb & 0x7f; + if (cbEnc > pCursor->cbLeft) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH_ENCODING, + "%s: Extended BER length field longer than available data: %#x vs %#x (uTag=%#x)", + pszErrorTag, cbEnc, pCursor->cbLeft, uTag); + switch (cbEnc) + { + case 1: + cb = pCursor->pbCur[0]; + break; + case 2: + cb = RT_MAKE_U16(pCursor->pbCur[1], pCursor->pbCur[0]); + break; + case 3: + cb = RT_MAKE_U32_FROM_U8(pCursor->pbCur[2], pCursor->pbCur[1], pCursor->pbCur[0], 0); + break; + case 4: + cb = RT_MAKE_U32_FROM_U8(pCursor->pbCur[3], pCursor->pbCur[2], pCursor->pbCur[1], pCursor->pbCur[0]); + break; + default: + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH_ENCODING, + "%s: Too long/short extended BER length field: %#x (uTag=%#x)", + pszErrorTag, cbEnc, uTag); + } + pCursor->cbLeft -= cbEnc; + pCursor->pbCur += cbEnc; + pAsn1Core->cbHdr += cbEnc; + + /* Check the length encoding efficiency (T-REC-X.690-200811 10.1, 9.1). */ + if (pCursor->fFlags & (RTASN1CURSOR_FLAGS_DER | RTASN1CURSOR_FLAGS_CER)) + { + if (cb <= 0x7f) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH_ENCODING, + "%s: Invalid DER/CER length encoding: cbEnc=%u cb=%#x uTag=%#x", + pszErrorTag, cbEnc, cb, uTag); + uint8_t cbNeeded; + if (cb <= 0x000000ff) cbNeeded = 1; + else if (cb <= 0x0000ffff) cbNeeded = 2; + else if (cb <= 0x00ffffff) cbNeeded = 3; + else cbNeeded = 4; + if (cbNeeded != cbEnc) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH_ENCODING, + "%s: Invalid DER/CER length encoding: cb=%#x uTag=%#x cbEnc=%u cbNeeded=%u", + pszErrorTag, cb, uTag, cbEnc, cbNeeded); + } + } + /* Indefinite form. */ + else if (pCursor->fFlags & RTASN1CURSOR_FLAGS_DER) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_INDEFINITE_LENGTH, + "%s: Indefinite length form not allowed in DER mode (uTag=%#x).", pszErrorTag, uTag); + else if (!(uTag & ASN1_TAGFLAG_CONSTRUCTED)) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_INDEFINITE_LENGTH, + "%s: Indefinite BER/CER encoding is for non-constructed tag (uTag=%#x)", pszErrorTag, uTag); + else if ( uTag != (ASN1_TAG_SEQUENCE | ASN1_TAGFLAG_CONSTRUCTED) + && uTag != (ASN1_TAG_SET | ASN1_TAGFLAG_CONSTRUCTED) + && (uTag & (ASN1_TAGFLAG_CONSTRUCTED | ASN1_TAGCLASS_CONTEXT)) + != (ASN1_TAGFLAG_CONSTRUCTED | ASN1_TAGCLASS_CONTEXT) ) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_INDEFINITE_LENGTH, + "%s: Indefinite BER/CER encoding not supported for this tag (uTag=%#x)", pszErrorTag, uTag); + else if (pCursor->fFlags & RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_INDEFINITE_LENGTH, + "%s: Nested indefinite BER/CER encoding. (uTag=%#x)", pszErrorTag, uTag); + else if (pCursor->cbLeft < 2) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_INDEFINITE_LENGTH, + "%s: Too little data left for indefinite BER/CER encoding (uTag=%#x)", pszErrorTag, uTag); + else + { + pCursor->fFlags |= RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH; + pAsn1Core->fFlags |= RTASN1CORE_F_INDEFINITE_LENGTH; + cb = pCursor->cbLeft; /* Start out with the whole sequence, adjusted later upon reach the end. */ + } + } + /* else if (cb == 0 && uTag == 0) { end of content } - callers handle this */ + + /* Check if the length makes sense. */ + if (cb > pCursor->cbLeft) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH, + "%s: BER value length out of bounds: %#x (max=%#x uTag=%#x)", + pszErrorTag, cb, pCursor->cbLeft, uTag); + + pAsn1Core->fFlags |= RTASN1CORE_F_PRESENT | RTASN1CORE_F_DECODED_CONTENT; + pAsn1Core->cb = cb; + pAsn1Core->uData.pv = (void *)pCursor->pbCur; + return VINF_SUCCESS; + } + + if (pCursor->cbLeft) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TOO_LITTLE_DATA_LEFT, + "%s: Too little data left to form a valid BER header", pszErrorTag); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NO_MORE_DATA, + "%s: No more data reading BER header", pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorMatchTagClassFlagsEx(PRTASN1CURSOR pCursor, PRTASN1CORE pAsn1Core, uint32_t uTag, uint32_t fClass, + bool fString, uint32_t fFlags, const char *pszErrorTag, const char *pszWhat) +{ + if (pAsn1Core->uTag == uTag) + { + if (pAsn1Core->fClass == fClass) + return VINF_SUCCESS; + if ( fString + && pAsn1Core->fClass == (fClass | ASN1_TAGFLAG_CONSTRUCTED)) + { + if (!(pCursor->fFlags & (RTASN1CURSOR_FLAGS_DER | RTASN1CURSOR_FLAGS_CER))) + return VINF_SUCCESS; + if (pCursor->fFlags & RTASN1CURSOR_FLAGS_CER) + { + if (pAsn1Core->cb > 1000) + return VINF_SUCCESS; + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING, + "%s: Constructed %s only allowed for >1000 byte in CER encoding: cb=%#x uTag=%#x fClass=%#x", + pszErrorTag, pszWhat, pAsn1Core->cb, pAsn1Core->uTag, pAsn1Core->fClass); + } + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING, + "%s: DER encoding does not allow constructed %s (cb=%#x uTag=%#x fClass=%#x)", + pszErrorTag, pszWhat, pAsn1Core->cb, pAsn1Core->uTag, pAsn1Core->fClass); + } + } + + if (fFlags & RTASN1CURSOR_GET_F_IMPLICIT) + { + pAsn1Core->fFlags |= RTASN1CORE_F_TAG_IMPLICIT; + pAsn1Core->uRealTag = uTag; + pAsn1Core->fRealClass = fClass; + return VINF_SUCCESS; + } + + return RTAsn1CursorSetInfo(pCursor, pAsn1Core->uTag != uTag ? VERR_ASN1_CURSOR_TAG_MISMATCH : VERR_ASN1_CURSOR_TAG_FLAG_CLASS_MISMATCH, + "%s: Unexpected %s type/flags: %#x/%#x (expected %#x/%#x)", + pszErrorTag, pszWhat, pAsn1Core->uTag, pAsn1Core->fClass, uTag, fClass); +} + + + +static int rtAsn1CursorGetXxxxCursor(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t uTag, uint8_t fClass, + PRTASN1CORE pAsn1Core, PRTASN1CURSOR pRetCursor, + const char *pszErrorTag, const char *pszWhat) +{ + int rc = RTAsn1CursorReadHdr(pCursor, pAsn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + if ( pAsn1Core->uTag == uTag + && pAsn1Core->fClass == fClass) + rc = VINF_SUCCESS; + else if (fFlags & RTASN1CURSOR_GET_F_IMPLICIT) + { + pAsn1Core->fFlags |= RTASN1CORE_F_TAG_IMPLICIT; + pAsn1Core->uRealTag = uTag; + pAsn1Core->fRealClass = fClass; + rc = VINF_SUCCESS; + } + else + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING, + "%s: Unexpected %s type/flags: %#x/%#x (expected %#x/%#x)", + pszErrorTag, pszWhat, pAsn1Core->uTag, pAsn1Core->fClass, uTag, fClass); + rc = RTAsn1CursorInitSub(pCursor, pAsn1Core->cb, pRetCursor, pszErrorTag); + if (RT_SUCCESS(rc)) + { + pAsn1Core->fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + return VINF_SUCCESS; + } + } + return rc; +} + + +RTDECL(int) RTAsn1CursorGetSequenceCursor(PRTASN1CURSOR pCursor, uint32_t fFlags, + PRTASN1SEQUENCECORE pSeqCore, PRTASN1CURSOR pSeqCursor, const char *pszErrorTag) +{ + return rtAsn1CursorGetXxxxCursor(pCursor, fFlags, ASN1_TAG_SEQUENCE, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED, + &pSeqCore->Asn1Core, pSeqCursor, pszErrorTag, "sequence"); +} + + +RTDECL(int) RTAsn1CursorGetSetCursor(PRTASN1CURSOR pCursor, uint32_t fFlags, + PRTASN1SETCORE pSetCore, PRTASN1CURSOR pSetCursor, const char *pszErrorTag) +{ + return rtAsn1CursorGetXxxxCursor(pCursor, fFlags, ASN1_TAG_SET, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED, + &pSetCore->Asn1Core, pSetCursor, pszErrorTag, "set"); +} + + +RTDECL(int) RTAsn1CursorGetContextTagNCursor(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t uExpectedTag, + PCRTASN1COREVTABLE pVtable, PRTASN1CONTEXTTAG pCtxTag, PRTASN1CURSOR pCtxTagCursor, + const char *pszErrorTag) +{ + int rc = rtAsn1CursorGetXxxxCursor(pCursor, fFlags, uExpectedTag, ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_CONSTRUCTED, + &pCtxTag->Asn1Core, pCtxTagCursor, pszErrorTag, "ctx tag"); + pCtxTag->Asn1Core.pOps = pVtable; + return rc; +} + + +RTDECL(int) RTAsn1CursorPeek(PRTASN1CURSOR pCursor, PRTASN1CORE pAsn1Core) +{ + uint32_t cbSavedLeft = pCursor->cbLeft; + uint8_t const *pbSavedCur = pCursor->pbCur; + uint8_t const fSavedFlags = pCursor->fFlags; + PRTERRINFO const pErrInfo = pCursor->pPrimary->pErrInfo; + pCursor->pPrimary->pErrInfo = NULL; + + int rc = RTAsn1CursorReadHdr(pCursor, pAsn1Core, "peek"); + + pCursor->pPrimary->pErrInfo = pErrInfo; + pCursor->pbCur = pbSavedCur; + pCursor->cbLeft = cbSavedLeft; + pCursor->fFlags = fSavedFlags; + return rc; +} + + +RTDECL(bool) RTAsn1CursorIsNextEx(PRTASN1CURSOR pCursor, uint32_t uTag, uint8_t fClass) +{ + RTASN1CORE Asn1Core; + int rc = RTAsn1CursorPeek(pCursor, &Asn1Core); + if (RT_SUCCESS(rc)) + return uTag == Asn1Core.uTag + && fClass == Asn1Core.fClass; + return false; +} + + +/** @name Legacy Interfaces. + * @{ */ +RTDECL(int) RTAsn1CursorGetCore(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1CORE pAsn1Core, const char *pszErrorTag) +{ + return RTAsn1Core_DecodeAsn1(pCursor, fFlags, pAsn1Core, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetNull(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1NULL pNull, const char *pszErrorTag) +{ + return RTAsn1Null_DecodeAsn1(pCursor, fFlags, pNull, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetInteger(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1INTEGER pInteger, const char *pszErrorTag) +{ + return RTAsn1Integer_DecodeAsn1(pCursor, fFlags, pInteger, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetBoolean(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1BOOLEAN pBoolean, const char *pszErrorTag) +{ + return RTAsn1Boolean_DecodeAsn1(pCursor, fFlags, pBoolean, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetObjId(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OBJID pObjId, const char *pszErrorTag) +{ + return RTAsn1ObjId_DecodeAsn1(pCursor, fFlags, pObjId, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetTime(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1TIME pTime, const char *pszErrorTag) +{ + return RTAsn1Time_DecodeAsn1(pCursor, fFlags, pTime, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetBitStringEx(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t cMaxBits, PRTASN1BITSTRING pBitString, + const char *pszErrorTag) +{ + return RTAsn1BitString_DecodeAsn1Ex(pCursor, fFlags, cMaxBits, pBitString, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetBitString(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1BITSTRING pBitString, const char *pszErrorTag) +{ + return RTAsn1BitString_DecodeAsn1(pCursor, fFlags, pBitString, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetOctetString(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OCTETSTRING pOctetString, + const char *pszErrorTag) +{ + return RTAsn1OctetString_DecodeAsn1(pCursor, fFlags, pOctetString, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetString(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pString, const char *pszErrorTag) +{ + return RTAsn1String_DecodeAsn1(pCursor, fFlags, pString, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetIa5String(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pString, const char *pszErrorTag) +{ + return RTAsn1Ia5String_DecodeAsn1(pCursor, fFlags, pString, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetUtf8String(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pString, const char *pszErrorTag) +{ + return RTAsn1Utf8String_DecodeAsn1(pCursor, fFlags, pString, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetBmpString(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pString, const char *pszErrorTag) +{ + return RTAsn1BmpString_DecodeAsn1(pCursor, fFlags, pString, pszErrorTag); +} + + +RTDECL(int) RTAsn1CursorGetDynType(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1DYNTYPE pDynType, const char *pszErrorTag) +{ + return RTAsn1DynType_DecodeAsn1(pCursor, fFlags, pDynType, pszErrorTag); +} +/** @} */ + diff --git a/src/VBox/Runtime/common/asn1/asn1-default-allocator.cpp b/src/VBox/Runtime/common/asn1/asn1-default-allocator.cpp new file mode 100644 index 00000000..ba04a854 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-default-allocator.cpp @@ -0,0 +1,231 @@ +/* $Id: asn1-default-allocator.cpp $ */ +/** @file + * IPRT - ASN.1, Default Allocator. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/mem.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + + +/** + * Aligns allocation sizes a little. + * + * @returns Aligned size. + * @param cb Requested size. + */ +static size_t rtAsn1DefaultAllocator_AlignSize(size_t cb) +{ + if (cb >= 64) + return RT_ALIGN_Z(cb, 64); + if (cb >= 32) + return RT_ALIGN_Z(cb, 32); + if (cb >= 16) + return RT_ALIGN_Z(cb, 16); + return cb; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFree} */ +static DECLCALLBACK(void) rtAsn1DefaultAllocator_Free(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, void *pv) +{ + RT_NOREF_PV(pThis); + RTMemFree(pv); + pAllocation->cbAllocated = 0; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnAlloc} */ +static DECLCALLBACK(int) rtAsn1DefaultAllocator_Alloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, + void **ppv, size_t cb) +{ + size_t cbAlloc = rtAsn1DefaultAllocator_AlignSize(cb); + void *pv = RTMemAllocZ(cbAlloc); + if (pv) + { + *ppv = pv; + pAllocation->cbAllocated = (uint32_t)cbAlloc; + return VINF_SUCCESS; + } + RT_NOREF_PV(pThis); + return VERR_NO_MEMORY; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnRealloc} */ +static DECLCALLBACK(int) rtAsn1DefaultAllocator_Realloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, + void *pvOld, void **ppvNew, size_t cbNew) +{ + Assert(pvOld); + Assert(cbNew); + size_t cbAlloc = rtAsn1DefaultAllocator_AlignSize(cbNew); + void *pv = RTMemRealloc(pvOld, cbAlloc); + if (pv) + { + *ppvNew = pv; + pAllocation->cbAllocated = (uint32_t)cbAlloc; + return VINF_SUCCESS; + } + RT_NOREF_PV(pThis); + return VERR_NO_MEMORY; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFreeArray} */ +static DECLCALLBACK(void) rtAsn1DefaultAllocator_FreeArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void **papvArray) +{ + RT_NOREF_PV(pThis); + Assert(papvArray); + Assert(pAllocation->cbEntry); + + uint32_t i = pAllocation->cEntriesAllocated; + while (i-- > 0) + RTMemFree(papvArray[i]); + RTMemFree(papvArray); + + pAllocation->cEntriesAllocated = 0; + pAllocation->cPointersAllocated = 0; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnGrowArray} */ +static DECLCALLBACK(int) rtAsn1DefaultAllocator_GrowArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void ***ppapvArray, uint32_t cMinEntries) +{ + RT_NOREF_PV(pThis); + + /* + * Resize the pointer array. We do chunks of 64 bytes for now. + */ + void **papvArray = *ppapvArray; + uint32_t cPointers = RT_ALIGN_32(cMinEntries, 64 / sizeof(void *)); + if (cPointers > pAllocation->cPointersAllocated) + { + void *pvPointers = RTMemRealloc(papvArray, cPointers * sizeof(void *)); + if (pvPointers) + { /* likely */ } + else if (cMinEntries > pAllocation->cPointersAllocated) + { + cPointers = cMinEntries; + pvPointers = RTMemRealloc(*ppapvArray, cPointers * sizeof(void *)); + if (!pvPointers) + return VERR_NO_MEMORY; + } + else + { + cPointers = pAllocation->cPointersAllocated; + pvPointers = papvArray; + } + + *ppapvArray = papvArray = (void **)pvPointers; + RT_BZERO(&papvArray[pAllocation->cPointersAllocated], (cPointers - pAllocation->cPointersAllocated) * sizeof(void *)); + pAllocation->cPointersAllocated = cPointers; + } + + /* + * Add more entries. Do multiple as the array grows. + * + * Note! We could possibly optimize this by allocating slabs of entries and + * slice them up. However, keep things as simple as possible for now. + */ + uint32_t cEntries = cMinEntries; + if (cEntries > 2) + { + if (cEntries > 8) + cEntries = RT_ALIGN_32(cEntries, 4); + else + cEntries = RT_ALIGN_32(cEntries, 2); + cEntries = RT_MIN(cEntries, cPointers); + Assert(cEntries >= cMinEntries); + } + Assert(cEntries <= pAllocation->cPointersAllocated); + + while (pAllocation->cEntriesAllocated < cEntries) + { + void *pv; + papvArray[pAllocation->cEntriesAllocated] = pv = RTMemAllocZ(pAllocation->cbEntry); + if (pv) + pAllocation->cEntriesAllocated++; + else if (pAllocation->cEntriesAllocated >= cMinEntries) + break; + else + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnShrinkArray} */ +static DECLCALLBACK(void) rtAsn1DefaultAllocator_ShrinkArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void ***ppapvArray, uint32_t cNew, uint32_t cCurrent) +{ + RT_NOREF_PV(pThis); + + /* + * For now we only zero the entries being removed. + */ + void **papvArray = *ppapvArray; + while (cNew < cCurrent) + { + RT_BZERO(papvArray[cNew], pAllocation->cbEntry); + cNew++; + } +} + + + +/** The default ASN.1 allocator. */ +#if 1 || !defined(IN_RING3) || defined(DOXYGEN_RUNNING) +RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1DefaultAllocator = +#else +RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1DefaultAllocatorDisabled = +#endif +{ + rtAsn1DefaultAllocator_Free, + rtAsn1DefaultAllocator_Alloc, + rtAsn1DefaultAllocator_Realloc, + rtAsn1DefaultAllocator_FreeArray, + rtAsn1DefaultAllocator_GrowArray, + rtAsn1DefaultAllocator_ShrinkArray +}; + diff --git a/src/VBox/Runtime/common/asn1/asn1-dump.cpp b/src/VBox/Runtime/common/asn1/asn1-dump.cpp new file mode 100644 index 00000000..e75d379e --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-dump.cpp @@ -0,0 +1,644 @@ +/* $Id: asn1-dump.cpp $ */ +/** @file + * IPRT - ASN.1, Structure Dumper. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/errcore.h> +#include <iprt/log.h> +#ifdef IN_RING3 +# include <iprt/stream.h> +#endif +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Dump data structure. + */ +typedef struct RTASN1DUMPDATA +{ + /** RTASN1DUMP_F_XXX. */ + uint32_t fFlags; + /** The printfv like output function. */ + PFNRTDUMPPRINTFV pfnPrintfV; + /** PrintfV user argument. */ + void *pvUser; +} RTASN1DUMPDATA; +/** Pointer to a dump data structure. */ +typedef RTASN1DUMPDATA *PRTASN1DUMPDATA; + + +#ifndef IN_SUP_HARDENED_R3 + +/* + * Since we're the only user of OIDs, this stuff lives here. + * Should that ever change, this code needs to move elsewhere and get it's own public API. + */ +# include "oiddb.h" + + +/** + * Searches a range in the big table for a key. + * + * @returns Pointer to the matching entry. NULL if not found. + * @param iEntry The start of the range. + * @param cEntries The number of entries in the range. + * @param uKey The key to find. + */ +DECLINLINE(PCRTOIDENTRYBIG) rtOidDbLookupBig(uint32_t iEntry, uint32_t cEntries, uint32_t uKey) +{ + /* Not worth doing binary search here, too few entries. */ + while (cEntries-- > 0) + { + uint32_t const uThisKey = g_aBigOidTable[iEntry].uKey; + if (uThisKey >= uKey) + { + if (uThisKey == uKey) + return &g_aBigOidTable[iEntry]; + break; + } + iEntry++; + } + return NULL; +} + + +/** + * Searches a range in the small table for a key. + * + * @returns Pointer to the matching entry. NULL if not found. + * @param iEntry The start of the range. + * @param cEntries The number of entries in the range. + * @param uKey The key to find. + */ +DECLINLINE(PCRTOIDENTRYSMALL) rtOidDbLookupSmall(uint32_t iEntry, uint32_t cEntries, uint32_t uKey) +{ + if (cEntries < 6) + { + /* Linear search for small ranges. */ + while (cEntries-- > 0) + { + uint32_t const uThisKey = g_aSmallOidTable[iEntry].uKey; + if (uThisKey >= uKey) + { + if (uThisKey == uKey) + return &g_aSmallOidTable[iEntry]; + break; + } + iEntry++; + } + } + else + { + /* Binary search. */ + uint32_t iEnd = iEntry + cEntries; + for (;;) + { + uint32_t const i = iEntry + (iEnd - iEntry) / 2; + uint32_t const uThisKey = g_aSmallOidTable[i].uKey; + if (uThisKey < uKey) + { + iEntry = i + 1; + if (iEntry >= iEnd) + break; + } + else if (uThisKey > uKey) + { + iEnd = i; + if (iEnd <= iEntry) + break; + } + else + return &g_aSmallOidTable[i]; + } + } + return NULL; +} + + +/** + * Queries the name for an object identifier. + * + * @returns IPRT status code (VINF_SUCCESS, VERR_NOT_FOUND, + * VERR_BUFFER_OVERFLOW) + * @param pauComponents The components making up the object ID. + * @param cComponents The number of components. + * @param pszDst Where to store the name if found. + * @param cbDst The size of the destination buffer. + */ +static int rtOidDbQueryObjIdName(uint32_t const *pauComponents, uint8_t cComponents, char *pszDst, size_t cbDst) +{ + int rc = VERR_NOT_FOUND; + if (cComponents > 0) + { + /* + * The top level is always in the small table as the range is restricted to 0,1,2. + */ + bool fBigTable = false; + uint32_t cEntries = RT_MIN(RT_ELEMENTS(g_aSmallOidTable), 3); + uint32_t iEntry = 0; + for (;;) + { + uint32_t const uKey = *pauComponents++; + if (!fBigTable) + { + PCRTOIDENTRYSMALL pSmallHit = rtOidDbLookupSmall(iEntry, cEntries, uKey); + if (pSmallHit) + { + if (--cComponents == 0) + { + if (RTBldProgStrTabQueryString(&g_OidDbStrTab, pSmallHit->offString, + pSmallHit->cchString, pszDst, cbDst) >= 0) + return VINF_SUCCESS; + rc = VERR_BUFFER_OVERFLOW; + break; + } + cEntries = pSmallHit->cChildren; + if (cEntries) + { + iEntry = pSmallHit->idxChildren; + fBigTable = pSmallHit->fBigTable; + continue; + } + } + } + else + { + PCRTOIDENTRYBIG pBigHit = rtOidDbLookupBig(iEntry, cEntries, uKey); + if (pBigHit) + { + if (--cComponents == 0) + { + if (RTBldProgStrTabQueryString(&g_OidDbStrTab, pBigHit->offString, + pBigHit->cchString, pszDst, cbDst) >= 0) + return VINF_SUCCESS; + rc = VERR_BUFFER_OVERFLOW; + break; + } + cEntries = pBigHit->cChildren; + if (cEntries) + { + iEntry = pBigHit->idxChildren; + fBigTable = pBigHit->fBigTable; + continue; + } + } + } + break; + } + } + + return rc; +} + + +/** + * Queries the name for an object identifier. + * + * This API is simple and more or less requires a + * + * @returns IPRT status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_NOT_FOUND if not found. + * @retval VERR_BUFFER_OVERFLOW if more buffer space is required. + * + * @param pauComponents The components making up the object ID. + * @param cComponents The number of components. + * @param pszDst Where to store the name if found. + * @param cbDst The size of the destination buffer. + */ +RTDECL(int) RTAsn1QueryObjIdName(PCRTASN1OBJID pObjId, char *pszDst, size_t cbDst) +{ + return rtOidDbQueryObjIdName(pObjId->pauComponents, pObjId->cComponents, pszDst, cbDst); +} + +#endif /* !IN_SUP_HARDENED_R3 */ + + + +/** + * Wrapper around FNRTASN1DUMPPRINTFV. + * + * @param pData The dump data structure. + * @param pszFormat Format string. + * @param ... Format arguments. + */ +static void rtAsn1DumpPrintf(PRTASN1DUMPDATA pData, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + pData->pfnPrintfV(pData->pvUser, pszFormat, va); + va_end(va); +} + + +/** + * Prints indentation. + * + * @param pData The dump data structure. + * @param uDepth The indentation depth. + */ +static void rtAsn1DumpPrintIdent(PRTASN1DUMPDATA pData, uint32_t uDepth) +{ + uint32_t cchLeft = uDepth * 2; + while (cchLeft > 0) + { + static char const s_szSpaces[] = " "; + uint32_t cch = RT_MIN(cchLeft, sizeof(s_szSpaces) - 1); + rtAsn1DumpPrintf(pData, &s_szSpaces[sizeof(s_szSpaces) - 1 - cch]); + cchLeft -= cch; + } +} + + +/** + * Dumps UTC TIME and GENERALIZED TIME + * + * @param pData The dump data structure. + * @param pAsn1Core The ASN.1 core object representation. + * @param pszType The time type name. + */ +static void rtAsn1DumpTime(PRTASN1DUMPDATA pData, PCRTASN1CORE pAsn1Core, const char *pszType) +{ + if ((pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT)) + { + PCRTASN1TIME pTime = (PCRTASN1TIME)pAsn1Core; + rtAsn1DumpPrintf(pData, "%s -- %04u-%02u-%02u %02u:%02u:%02.%09Z\n", + pszType, + pTime->Time.i32Year, pTime->Time.u8Month, pTime->Time.u8MonthDay, + pTime->Time.u8Hour, pTime->Time.u8Minute, pTime->Time.u8Second, + pTime->Time.u32Nanosecond); + } + else if (pAsn1Core->cb > 0 && pAsn1Core->cb < 32 && pAsn1Core->uData.pch) + rtAsn1DumpPrintf(pData, "%s '%.*s'\n", pszType, (size_t)pAsn1Core->cb, pAsn1Core->uData.pch); + else + rtAsn1DumpPrintf(pData, "%s -- cb=%u\n", pszType, pAsn1Core->cb); +} + + +/** + * Dumps strings sharing the RTASN1STRING structure. + * + * @param pData The dump data structure. + * @param pAsn1Core The ASN.1 core object representation. + * @param pszType The string type name. + * @param uDepth The current identation level. + */ +static void rtAsn1DumpString(PRTASN1DUMPDATA pData, PCRTASN1CORE pAsn1Core, const char *pszType, uint32_t uDepth) +{ + rtAsn1DumpPrintf(pData, "%s", pszType); + + const char *pszPostfix = "'\n"; + bool fUtf8 = false; + const char *pch = pAsn1Core->uData.pch; + uint32_t cch = pAsn1Core->cb; + PCRTASN1STRING pString = (PCRTASN1STRING)pAsn1Core; + if ( (pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT) + && pString->pszUtf8 + && pString->cchUtf8) + { + fUtf8 = true; + pszPostfix = "' -- utf-8\n"; + } + + if (cch == 0 || !pch) + rtAsn1DumpPrintf(pData, "-- cb=%u\n", pszType, pAsn1Core->cb); + else + { + if (cch >= 48) + { + rtAsn1DumpPrintf(pData, "\n"); + rtAsn1DumpPrintIdent(pData, uDepth + 1); + } + rtAsn1DumpPrintf(pData, " '"); + + /** @todo Handle BMP and UNIVERSIAL strings specially. */ + do + { + const char *pchStart = pch; + while ( cch > 0 + && (uint8_t)*pch >= 0x20 + && (!fUtf8 ? (uint8_t)*pch < 0x7f : (uint8_t)*pch != 0x7f) + && *pch != '\'') + cch--, pch++; + if (pchStart != pch) + rtAsn1DumpPrintf(pData, "%.*s", pch - pchStart, pchStart); + + while ( cch > 0 + && ( (uint8_t)*pch < 0x20 + || (!fUtf8 ? (uint8_t)*pch >= 0x7f : (uint8_t)*pch == 0x7f) + || (uint8_t)*pch == '\'') ) + { + rtAsn1DumpPrintf(pData, "\\x%02x", *pch); + cch--; + pch++; + } + } while (cch > 0); + + rtAsn1DumpPrintf(pData, pszPostfix); + } +} + + +/** + * Dumps the type and value of an universal ASN.1 type. + * + * @returns True if it opens a child, false if not. + * @param pData The dumper data. + * @param pAsn1Core The ASN.1 object to dump. + * @param uDepth The current depth (for indentation). + */ +static bool rtAsn1DumpUniversalTypeAndValue(PRTASN1DUMPDATA pData, PCRTASN1CORE pAsn1Core, uint32_t uDepth) +{ + const char *pszValuePrefix = "-- value:"; + const char *pszDefault = ""; + if (pAsn1Core->fFlags & RTASN1CORE_F_DEFAULT) + { + pszValuePrefix = "DEFAULT"; + pszDefault = "DEFAULT "; + } + + bool fOpen = false; + switch (pAsn1Core->uRealTag) + { + case ASN1_TAG_BOOLEAN: + if (pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT) + rtAsn1DumpPrintf(pData, "BOOLEAN %s %RTbool\n", pszValuePrefix, ((PCRTASN1BOOLEAN)pAsn1Core)->fValue); + else if (pAsn1Core->cb == 1 && pAsn1Core->uData.pu8) + rtAsn1DumpPrintf(pData, "BOOLEAN %s %u\n", pszValuePrefix, *pAsn1Core->uData.pu8); + else + rtAsn1DumpPrintf(pData, "BOOLEAN -- cb=%u\n", pAsn1Core->cb); + break; + + case ASN1_TAG_INTEGER: + if ((pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT) && pAsn1Core->cb <= 8) + rtAsn1DumpPrintf(pData, "INTEGER %s %llu / %#llx\n", pszValuePrefix, + ((PCRTASN1INTEGER)pAsn1Core)->uValue, ((PCRTASN1INTEGER)pAsn1Core)->uValue); + else if (pAsn1Core->cb == 0 || pAsn1Core->cb >= 512 || !pAsn1Core->uData.pu8) + rtAsn1DumpPrintf(pData, "INTEGER -- cb=%u\n", pAsn1Core->cb); + else if (pAsn1Core->cb <= 32) + rtAsn1DumpPrintf(pData, "INTEGER %s %.*Rhxs\n", pszValuePrefix, (size_t)pAsn1Core->cb, pAsn1Core->uData.pu8); + else + rtAsn1DumpPrintf(pData, "INTEGER %s\n%.*Rhxd\n", pszValuePrefix, (size_t)pAsn1Core->cb, pAsn1Core->uData.pu8); + break; + + case ASN1_TAG_BIT_STRING: + if ((pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT)) + { + PCRTASN1BITSTRING pBitString = (PCRTASN1BITSTRING)pAsn1Core; + rtAsn1DumpPrintf(pData, "BIT STRING %s-- cb=%u cBits=%#x cMaxBits=%#x", + pszDefault, pBitString->Asn1Core.cb, pBitString->cBits, pBitString->cMaxBits); + if (pBitString->cBits <= 64) + rtAsn1DumpPrintf(pData, " value=%#llx\n", RTAsn1BitString_GetAsUInt64(pBitString)); + else + rtAsn1DumpPrintf(pData, "\n"); + } + else + rtAsn1DumpPrintf(pData, "BIT STRING %s-- cb=%u\n", pszDefault, pAsn1Core->cb); + fOpen = pAsn1Core->pOps != NULL; + break; + + case ASN1_TAG_OCTET_STRING: + rtAsn1DumpPrintf(pData, "OCTET STRING %s-- cb=%u\n", pszDefault, pAsn1Core->cb); + fOpen = pAsn1Core->pOps != NULL; + break; + + case ASN1_TAG_NULL: + rtAsn1DumpPrintf(pData, "NULL\n"); + break; + + case ASN1_TAG_OID: + if ((pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT)) + { +#ifndef IN_SUP_HARDENED_R3 + PCRTASN1OBJID pObjId = (PCRTASN1OBJID)pAsn1Core; + char szName[64]; + if (rtOidDbQueryObjIdName(pObjId->pauComponents, pObjId->cComponents, szName, sizeof(szName)) == VINF_SUCCESS) + rtAsn1DumpPrintf(pData, "OBJECT IDENTIFIER %s%s ('%s')\n", + pszDefault, szName, ((PCRTASN1OBJID)pAsn1Core)->szObjId); + else +#endif + rtAsn1DumpPrintf(pData, "OBJECT IDENTIFIER %s'%s'\n", pszDefault, ((PCRTASN1OBJID)pAsn1Core)->szObjId); + } + else + rtAsn1DumpPrintf(pData, "OBJECT IDENTIFIER %s -- cb=%u\n", pszDefault, pAsn1Core->cb); + break; + + case ASN1_TAG_OBJECT_DESCRIPTOR: + rtAsn1DumpPrintf(pData, "OBJECT DESCRIPTOR -- cb=%u TODO\n", pAsn1Core->cb); + break; + + case ASN1_TAG_EXTERNAL: + rtAsn1DumpPrintf(pData, "EXTERNAL -- cb=%u TODO\n", pAsn1Core->cb); + break; + + case ASN1_TAG_REAL: + rtAsn1DumpPrintf(pData, "REAL -- cb=%u TODO\n", pAsn1Core->cb); + break; + + case ASN1_TAG_ENUMERATED: + rtAsn1DumpPrintf(pData, "ENUMERATED -- cb=%u TODO\n", pAsn1Core->cb); + break; + + case ASN1_TAG_EMBEDDED_PDV: + rtAsn1DumpPrintf(pData, "EMBEDDED PDV -- cb=%u TODO\n", pAsn1Core->cb); + break; + + case ASN1_TAG_UTF8_STRING: + rtAsn1DumpString(pData, pAsn1Core, "UTF8 STRING", uDepth); + break; + + case ASN1_TAG_RELATIVE_OID: + rtAsn1DumpPrintf(pData, "RELATIVE OBJECT IDENTIFIER -- cb=%u TODO\n", pAsn1Core->cb); + break; + + case ASN1_TAG_SEQUENCE: + rtAsn1DumpPrintf(pData, "SEQUENCE -- cb=%u\n", pAsn1Core->cb); + fOpen = true; + break; + case ASN1_TAG_SET: + rtAsn1DumpPrintf(pData, "SET -- cb=%u\n", pAsn1Core->cb); + fOpen = true; + break; + + case ASN1_TAG_NUMERIC_STRING: + rtAsn1DumpString(pData, pAsn1Core, "NUMERIC STRING", uDepth); + break; + + case ASN1_TAG_PRINTABLE_STRING: + rtAsn1DumpString(pData, pAsn1Core, "PRINTABLE STRING", uDepth); + break; + + case ASN1_TAG_T61_STRING: + rtAsn1DumpString(pData, pAsn1Core, "T61 STRING", uDepth); + break; + + case ASN1_TAG_VIDEOTEX_STRING: + rtAsn1DumpString(pData, pAsn1Core, "VIDEOTEX STRING", uDepth); + break; + + case ASN1_TAG_IA5_STRING: + rtAsn1DumpString(pData, pAsn1Core, "IA5 STRING", uDepth); + break; + + case ASN1_TAG_GRAPHIC_STRING: + rtAsn1DumpString(pData, pAsn1Core, "GRAPHIC STRING", uDepth); + break; + + case ASN1_TAG_VISIBLE_STRING: + rtAsn1DumpString(pData, pAsn1Core, "VISIBLE STRING", uDepth); + break; + + case ASN1_TAG_GENERAL_STRING: + rtAsn1DumpString(pData, pAsn1Core, "GENERAL STRING", uDepth); + break; + + case ASN1_TAG_UNIVERSAL_STRING: + rtAsn1DumpString(pData, pAsn1Core, "UNIVERSAL STRING", uDepth); + break; + + case ASN1_TAG_BMP_STRING: + rtAsn1DumpString(pData, pAsn1Core, "BMP STRING", uDepth); + break; + + case ASN1_TAG_UTC_TIME: + rtAsn1DumpTime(pData, pAsn1Core, "UTC TIME"); + break; + + case ASN1_TAG_GENERALIZED_TIME: + rtAsn1DumpTime(pData, pAsn1Core, "GENERALIZED TIME"); + break; + + case ASN1_TAG_CHARACTER_STRING: + rtAsn1DumpPrintf(pData, "CHARACTER STRING -- cb=%u TODO\n", pAsn1Core->cb); + break; + + default: + rtAsn1DumpPrintf(pData, "[UNIVERSAL %u]\n", pAsn1Core->uTag); + break; + } + return fOpen; +} + + +/** @callback_method_impl{FNRTASN1ENUMCALLBACK} */ +static DECLCALLBACK(int) rtAsn1DumpEnumCallback(PRTASN1CORE pAsn1Core, const char *pszName, uint32_t uDepth, void *pvUser) +{ + PRTASN1DUMPDATA pData = (PRTASN1DUMPDATA)pvUser; + if (!pAsn1Core->fFlags) + return VINF_SUCCESS; + + bool fOpen = false; + rtAsn1DumpPrintIdent(pData, uDepth); + switch (pAsn1Core->fClass & ASN1_TAGCLASS_MASK) + { + case ASN1_TAGCLASS_UNIVERSAL: + rtAsn1DumpPrintf(pData, "%-16s ", pszName); + fOpen = rtAsn1DumpUniversalTypeAndValue(pData, pAsn1Core, uDepth); + break; + + case ASN1_TAGCLASS_CONTEXT: + if ((pAsn1Core->fRealClass & ASN1_TAGCLASS_MASK) == ASN1_TAGCLASS_UNIVERSAL) + { + rtAsn1DumpPrintf(pData, "%-16s [%u] ", pszName, pAsn1Core->uTag); + fOpen = rtAsn1DumpUniversalTypeAndValue(pData, pAsn1Core, uDepth); + } + else + { + rtAsn1DumpPrintf(pData, "%-16s [%u]\n", pszName, pAsn1Core->uTag); + fOpen = true; + } + break; + + case ASN1_TAGCLASS_APPLICATION: + if ((pAsn1Core->fRealClass & ASN1_TAGCLASS_MASK) == ASN1_TAGCLASS_UNIVERSAL) + { + rtAsn1DumpPrintf(pData, "%-16s [APPLICATION %u] ", pszName, pAsn1Core->uTag); + fOpen = rtAsn1DumpUniversalTypeAndValue(pData, pAsn1Core, uDepth); + } + else + { + rtAsn1DumpPrintf(pData, "%-16s [APPLICATION %u]\n", pszName, pAsn1Core->uTag); + fOpen = true; + } + break; + + case ASN1_TAGCLASS_PRIVATE: + if (RTASN1CORE_IS_DUMMY(pAsn1Core)) + rtAsn1DumpPrintf(pData, "%-16s DUMMY\n", pszName); + else + { + rtAsn1DumpPrintf(pData, "%-16s [PRIVATE %u]\n", pszName, pAsn1Core->uTag); + fOpen = true; + } + break; + } + /** @todo {} */ + + /* + * Recurse. + */ + if ( pAsn1Core->pOps + && pAsn1Core->pOps->pfnEnum) + pAsn1Core->pOps->pfnEnum(pAsn1Core, rtAsn1DumpEnumCallback, uDepth, pData); + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Dump(PCRTASN1CORE pAsn1Core, uint32_t fFlags, uint32_t uLevel, PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser) +{ + if ( pAsn1Core->pOps + && pAsn1Core->pOps->pfnEnum) + { + RTASN1DUMPDATA Data; + Data.fFlags = fFlags; + Data.pfnPrintfV = pfnPrintfV; + Data.pvUser = pvUser; + + return pAsn1Core->pOps->pfnEnum((PRTASN1CORE)pAsn1Core, rtAsn1DumpEnumCallback, uLevel, &Data); + } + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/common/asn1/asn1-efence-allocator.cpp b/src/VBox/Runtime/common/asn1/asn1-efence-allocator.cpp new file mode 100644 index 00000000..ed122378 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-efence-allocator.cpp @@ -0,0 +1,215 @@ +/* $Id: asn1-efence-allocator.cpp $ */ +/** @file + * IPRT - ASN.1, Electric Fense Allocator. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/mem.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFree} */ +static DECLCALLBACK(void) rtAsn1EFenceAllocator_Free(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, void *pv) +{ + RT_NOREF_PV(pThis); + RTMemEfFreeNP(pv); + pAllocation->cbAllocated = 0; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnAlloc} */ +static DECLCALLBACK(int) rtAsn1EFenceAllocator_Alloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, + void **ppv, size_t cb) +{ + void *pv = RTMemEfAllocZNP(cb, RTMEM_TAG); + if (pv) + { + *ppv = pv; + pAllocation->cbAllocated = (uint32_t)cb; + return VINF_SUCCESS; + } + RT_NOREF_PV(pThis); + return VERR_NO_MEMORY; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnRealloc} */ +static DECLCALLBACK(int) rtAsn1EFenceAllocator_Realloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, + void *pvOld, void **ppvNew, size_t cbNew) +{ + Assert(pvOld); + Assert(cbNew); + void *pv = RTMemEfReallocNP(pvOld, cbNew, RTMEM_TAG); + if (pv) + { + *ppvNew = pv; + pAllocation->cbAllocated = (uint32_t)cbNew; + return VINF_SUCCESS; + } + RT_NOREF_PV(pThis); + return VERR_NO_MEMORY; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFreeArray} */ +static DECLCALLBACK(void) rtAsn1EFenceAllocator_FreeArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void **papvArray) +{ + RT_NOREF_PV(pThis); + Assert(papvArray); + Assert(pAllocation->cbEntry); + Assert(pAllocation->cEntriesAllocated <= pAllocation->cPointersAllocated); + + uint32_t i = pAllocation->cEntriesAllocated; + while (i-- > 0) + { + RTMemEfFreeNP(papvArray[i]); + papvArray[i] = NULL; + } + RTMemEfFreeNP(papvArray); + + pAllocation->cEntriesAllocated = 0; + pAllocation->cPointersAllocated = 0; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnGrowArray} */ +static DECLCALLBACK(int) rtAsn1EFenceAllocator_GrowArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void ***ppapvArray, uint32_t cMinEntries) +{ + RT_NOREF_PV(pThis); + Assert(pAllocation->cbEntry); + Assert(pAllocation->cEntriesAllocated <= pAllocation->cPointersAllocated); + + /* + * Resize the pointer array. + */ + void **papvArray = *ppapvArray; + void *pvPointers = RTMemEfReallocNP(papvArray, cMinEntries * sizeof(void *), RTMEM_TAG); + if (pvPointers) + { + *ppapvArray = papvArray = (void **)pvPointers; + if (cMinEntries > pAllocation->cPointersAllocated) /* possible on multiple shrink failures */ + RT_BZERO(&papvArray[pAllocation->cPointersAllocated], + (cMinEntries - pAllocation->cPointersAllocated) * sizeof(void *)); + else + AssertFailed(); + pAllocation->cPointersAllocated = cMinEntries; + } + else if (cMinEntries > pAllocation->cPointersAllocated) + return VERR_NO_MEMORY; + /* else: possible but unlikely */ + + /* + * Add more entries. + */ + while (pAllocation->cEntriesAllocated < cMinEntries) + { + void *pv; + papvArray[pAllocation->cEntriesAllocated] = pv = RTMemEfAllocZNP(pAllocation->cbEntry, RTMEM_TAG); + if (pv) + pAllocation->cEntriesAllocated++; + else + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnShrinkArray} */ +static DECLCALLBACK(void) rtAsn1EFenceAllocator_ShrinkArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void ***ppapvArray, uint32_t cNew, uint32_t cCurrent) +{ + RT_NOREF_PV(pThis); + Assert(pAllocation->cbEntry); + Assert(pAllocation->cEntriesAllocated <= pAllocation->cPointersAllocated); + + /* + * We always free and resize. + */ + Assert(pAllocation->cEntriesAllocated == cCurrent); + Assert(cNew < cCurrent); + + /* Free entries. */ + void **papvArray = *ppapvArray; + while (cCurrent-- > cNew) + { + RTMemEfFreeNP(papvArray[cCurrent]); + papvArray[cCurrent] = NULL; + } + pAllocation->cEntriesAllocated = cNew; + + /* Try resize pointer array. Failure here is a genuine possibility since the + efence code will try allocate a new block. This causes extra fun in the + grow method above. */ + void *pvPointers = RTMemEfReallocNP(papvArray, cNew * sizeof(void *), RTMEM_TAG); + if (pvPointers) + { + *ppapvArray = (void **)pvPointers; + pAllocation->cPointersAllocated = cNew; + } +} + + +/** The Electric Fence ASN.1 allocator. */ +RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1EFenceAllocator = +{ + rtAsn1EFenceAllocator_Free, + rtAsn1EFenceAllocator_Alloc, + rtAsn1EFenceAllocator_Realloc, + rtAsn1EFenceAllocator_FreeArray, + rtAsn1EFenceAllocator_GrowArray, + rtAsn1EFenceAllocator_ShrinkArray +}; + +#if 0 && defined(IN_RING3) /* for efence testing */ +RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1DefaultAllocator = +{ + rtAsn1EFenceAllocator_Free, + rtAsn1EFenceAllocator_Alloc, + rtAsn1EFenceAllocator_Realloc, + rtAsn1EFenceAllocator_FreeArray, + rtAsn1EFenceAllocator_GrowArray, + rtAsn1EFenceAllocator_ShrinkArray +}; +#endif + diff --git a/src/VBox/Runtime/common/asn1/asn1-encode.cpp b/src/VBox/Runtime/common/asn1/asn1-encode.cpp new file mode 100644 index 00000000..d3beb090 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-encode.cpp @@ -0,0 +1,535 @@ +/* $Id: asn1-encode.cpp $ */ +/** @file + * IPRT - ASN.1, Encoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/assert.h> +#include <iprt/bignum.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Argument package for rtAsn1EncodePrepareCallback passed by RTAsn1EncodePrepare. + */ +typedef struct RTASN1ENCODEPREPARGS +{ + /** The size at this level. */ + uint32_t cb; + /** RTASN1ENCODE_F_XXX. */ + uint32_t fFlags; + /** Pointer to the error info. (optional) */ + PRTERRINFO pErrInfo; +} RTASN1ENCODEPREPARGS; + + +/** + * Argument package for rtAsn1EncodeWriteCallback passed by RTAsn1EncodeWrite. + */ +typedef struct RTASN1ENCODEWRITEARGS +{ + /** RTASN1ENCODE_F_XXX. */ + uint32_t fFlags; + /** Pointer to the writer funtion. */ + PFNRTASN1ENCODEWRITER pfnWriter; + /** User argument to the writer function. */ + void *pvUser; + /** Pointer to the error info. (optional) */ + PRTERRINFO pErrInfo; +} RTASN1ENCODEWRITEARGS; + +/** + * Argument package for rtAsn1EncodeToBufferCallback passed by + * RTAsn1EncodeToBuffer. + */ +typedef struct RTASN1ENCODETOBUFARGS +{ + /** The destination buffer position (incremented while writing). */ + uint8_t *pbDst; + /** The size of the destination buffer left (decremented while writing). */ + size_t cbDst; +} RTASN1ENCODETOBUFARGS; + + +RTDECL(int) RTAsn1EncodeRecalcHdrSize(PRTASN1CORE pAsn1Core, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + AssertReturn((fFlags & RTASN1ENCODE_F_RULE_MASK) == RTASN1ENCODE_F_DER, VERR_INVALID_FLAGS); + int rc = VINF_SUCCESS; + + uint8_t cbHdr; + if ((pAsn1Core->fFlags & (RTASN1CORE_F_PRESENT | RTASN1CORE_F_DUMMY | RTASN1CORE_F_DEFAULT)) == RTASN1CORE_F_PRESENT) + { + /* + * The minimum header size is two bytes. + */ + cbHdr = 2; + + /* + * Add additional bytes for encoding the tag. + */ + uint32_t uTag = pAsn1Core->uTag; + if (uTag >= ASN1_TAG_USE_LONG_FORM) + { + AssertReturn(pAsn1Core->uTag != UINT32_MAX, RTErrInfoSet(pErrInfo, VERR_ASN1_DUMMY_OBJECT, "uTag=UINT32_MAX")); + do + { + cbHdr++; + uTag >>= 7; + } while (uTag > 0); + } + + /* + * Add additional bytes for encoding the content length. + */ + uint32_t cb = pAsn1Core->cb; + if (cb >= 0x80) + { + AssertReturn(cb < _1G, RTErrInfoSetF(pErrInfo, VERR_ASN1_TOO_LONG, "cb=%u (%#x)", cb, cb)); + + if (cb <= UINT32_C(0xffff)) + { + if (cb <= UINT32_C(0xff)) + cbHdr += 1; + else + cbHdr += 2; + } + else + { + if (cb <= UINT32_C(0xffffff)) + cbHdr += 3; + else + cbHdr += 4; + } + } + } + /* + * Not present, dummy or otherwise not encoded. + */ + else + { + cbHdr = 0; + if (pAsn1Core->fFlags & RTASN1CORE_F_DEFAULT) + rc = VINF_ASN1_NOT_ENCODED; + else + { + Assert(RTASN1CORE_IS_DUMMY(pAsn1Core)); + Assert(pAsn1Core->pOps && pAsn1Core->pOps->pfnEnum); + rc = VINF_SUCCESS; + } + } + + /* + * Update the header length. + */ + pAsn1Core->cbHdr = cbHdr; + return rc; +} + + +/** + * @callback_method_impl{FNRTASN1ENUMCALLBACK} + */ +static DECLCALLBACK(int) rtAsn1EncodePrepareCallback(PRTASN1CORE pAsn1Core, const char *pszName, uint32_t uDepth, void *pvUser) +{ + RTASN1ENCODEPREPARGS *pArgs = (RTASN1ENCODEPREPARGS *)pvUser; + RT_NOREF_PV(pszName); + if (RTASN1CORE_IS_PRESENT(pAsn1Core)) + { + /* + * Depth first, where relevant. + */ + uint32_t const cbSaved = pArgs->cb; + if (pAsn1Core->pOps) + { + /* + * Use the encoding preparation method when available. + */ + int rc; + if (pAsn1Core->pOps->pfnEncodePrep) + rc = pAsn1Core->pOps->pfnEncodePrep(pAsn1Core, pArgs->fFlags, pArgs->pErrInfo); + else if (pAsn1Core->pOps->pfnEnum) + { + /* + * Recurse to prepare the child objects (if any). + */ + rc = pAsn1Core->pOps->pfnEnum(pAsn1Core, rtAsn1EncodePrepareCallback, uDepth + 1, pArgs); + if (RT_SUCCESS(rc)) + pAsn1Core->cb = pArgs->cb - cbSaved; + } + else + { + /* + * Must be a primitive type if DER. + */ + if ( (pAsn1Core->fClass & ASN1_TAGFLAG_CONSTRUCTED) + && (pArgs->fFlags & RTASN1ENCODE_F_DER) ) + return RTErrInfoSetF(pArgs->pErrInfo, VERR_ASN1_EXPECTED_PRIMITIVE, + "Expected primitive ASN.1 object: uTag=%#x fClass=%#x cb=%u", + RTASN1CORE_GET_TAG(pAsn1Core), pAsn1Core->fClass, pAsn1Core->cb); + rc = VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + rc = RTAsn1EncodeRecalcHdrSize(pAsn1Core, pArgs->fFlags, pArgs->pErrInfo); + if (RT_FAILURE(rc)) + return rc; + } + else + { + AssertFailed(); + pAsn1Core->cb = 0; + pAsn1Core->cbHdr = 0; + } + + /* + * Recalculate the output size, thus far. Dummy objects propagates the + * content size, but the header size is zero. Other objects with + * header size zero are not encoded and should be omitted entirely. + */ + if (pAsn1Core->cbHdr > 0 || RTASN1CORE_IS_DUMMY(pAsn1Core)) + pArgs->cb = RTASN1CORE_GET_RAW_ASN1_SIZE(pAsn1Core) + cbSaved; + else + pArgs->cb = cbSaved; + } + + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1EncodePrepare(PRTASN1CORE pRoot, uint32_t fFlags, uint32_t *pcbEncoded, PRTERRINFO pErrInfo) +{ + AssertReturn((fFlags & RTASN1ENCODE_F_RULE_MASK) == RTASN1ENCODE_F_DER, VERR_INVALID_FLAGS); + + /* + * This is implemented as a recursive enumeration of the ASN.1 object structure. + */ + RTASN1ENCODEPREPARGS Args; + Args.cb = 0; + Args.fFlags = fFlags; + Args.pErrInfo = pErrInfo; + int rc = rtAsn1EncodePrepareCallback(pRoot, "root", 0, &Args); + if (pcbEncoded) + *pcbEncoded = RTASN1CORE_GET_RAW_ASN1_SIZE(pRoot); + return rc; +} + + +RTDECL(int) RTAsn1EncodeWriteHeader(PCRTASN1CORE pAsn1Core, uint32_t fFlags, FNRTASN1ENCODEWRITER pfnWriter, void *pvUser, + PRTERRINFO pErrInfo) +{ + AssertReturn((fFlags & RTASN1ENCODE_F_RULE_MASK) == RTASN1ENCODE_F_DER, VERR_INVALID_FLAGS); + + if ((pAsn1Core->fFlags & (RTASN1CORE_F_PRESENT | RTASN1CORE_F_DUMMY | RTASN1CORE_F_DEFAULT)) == RTASN1CORE_F_PRESENT) + { + uint8_t abHdr[16]; /* 2 + max 5 tag + max 4 length = 11 */ + uint8_t *pbDst = &abHdr[0]; + + /* + * Encode the tag. + */ + uint32_t uTag = pAsn1Core->uTag; + if (uTag < ASN1_TAG_USE_LONG_FORM) + *pbDst++ = (uint8_t)uTag | (pAsn1Core->fClass & ~ASN1_TAG_MASK); + else + { + AssertReturn(pAsn1Core->uTag != UINT32_MAX, RTErrInfoSet(pErrInfo, VERR_ASN1_DUMMY_OBJECT, "uTag=UINT32_MAX")); + + /* In the long form, the tag is encoded MSB style with the 8th bit + of each byte indicating the whether there are more byte. */ + *pbDst++ = ASN1_TAG_USE_LONG_FORM | (pAsn1Core->fClass & ~ASN1_TAG_MASK); + if (uTag <= UINT32_C(0x7f)) + *pbDst++ = uTag; + else if (uTag <= UINT32_C(0x3fff)) /* 2**(7*2) = 0x4000 (16384) */ + { + *pbDst++ = (uTag >> 7) | 0x80; + *pbDst++ = uTag & 0x7f; + } + else if (uTag <= UINT32_C(0x1fffff)) /* 2**(7*3) = 0x200000 (2097152) */ + { + *pbDst++ = (uTag >> 14) | 0x80; + *pbDst++ = ((uTag >> 7) & 0x7f) | 0x80; + *pbDst++ = uTag & 0x7f; + } + else if (uTag <= UINT32_C(0xfffffff)) /* 2**(7*4) = 0x10000000 (268435456) */ + { + *pbDst++ = (uTag >> 21) | 0x80; + *pbDst++ = ((uTag >> 14) & 0x7f) | 0x80; + *pbDst++ = ((uTag >> 7) & 0x7f) | 0x80; + *pbDst++ = uTag & 0x7f; + } + else + { + *pbDst++ = (uTag >> 28) | 0x80; + *pbDst++ = ((uTag >> 21) & 0x7f) | 0x80; + *pbDst++ = ((uTag >> 14) & 0x7f) | 0x80; + *pbDst++ = ((uTag >> 7) & 0x7f) | 0x80; + *pbDst++ = uTag & 0x7f; + } + } + + /* + * Encode the length. + */ + uint32_t cb = pAsn1Core->cb; + if (cb < 0x80) + *pbDst++ = (uint8_t)cb; + else + { + AssertReturn(cb < _1G, RTErrInfoSetF(pErrInfo, VERR_ASN1_TOO_LONG, "cb=%u (%#x)", cb, cb)); + + if (cb <= UINT32_C(0xffff)) + { + if (cb <= UINT32_C(0xff)) + { + pbDst[0] = 0x81; + pbDst[1] = (uint8_t)cb; + pbDst += 2; + } + else + { + pbDst[0] = 0x82; + pbDst[1] = cb >> 8; + pbDst[2] = (uint8_t)cb; + pbDst += 3; + } + } + else + { + if (cb <= UINT32_C(0xffffff)) + { + pbDst[0] = 0x83; + pbDst[1] = (uint8_t)(cb >> 16); + pbDst[2] = (uint8_t)(cb >> 8); + pbDst[3] = (uint8_t)cb; + pbDst += 4; + } + else + { + pbDst[0] = 0x84; + pbDst[1] = (uint8_t)(cb >> 24); + pbDst[2] = (uint8_t)(cb >> 16); + pbDst[3] = (uint8_t)(cb >> 8); + pbDst[4] = (uint8_t)cb; + pbDst += 5; + } + } + } + + size_t const cbHdr = pbDst - &abHdr[0]; + Assert(sizeof(abHdr) >= cbHdr); + Assert(pAsn1Core->cbHdr == cbHdr); + + /* + * Write it. + */ + return pfnWriter(abHdr, cbHdr, pvUser, pErrInfo); + } + + /* + * Not present, dummy or otherwise not encoded. + */ + Assert(pAsn1Core->cbHdr == 0); + if (pAsn1Core->fFlags & RTASN1CORE_F_DEFAULT) + return VINF_ASN1_NOT_ENCODED; + Assert(RTASN1CORE_IS_DUMMY(pAsn1Core)); + Assert(pAsn1Core->pOps && pAsn1Core->pOps->pfnEnum); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNRTASN1ENUMCALLBACK} + */ +static DECLCALLBACK(int) rtAsn1EncodeWriteCallback(PRTASN1CORE pAsn1Core, const char *pszName, uint32_t uDepth, void *pvUser) +{ + RTASN1ENCODEWRITEARGS *pArgs = (RTASN1ENCODEWRITEARGS *)pvUser; + RT_NOREF_PV(pszName); + int rc; + if (RTASN1CORE_IS_PRESENT(pAsn1Core)) + { + /* + * If there is an write method, use it. + */ + if ( pAsn1Core->pOps + && pAsn1Core->pOps->pfnEncodeWrite) + rc = pAsn1Core->pOps->pfnEncodeWrite(pAsn1Core, pArgs->fFlags, pArgs->pfnWriter, pArgs->pvUser, pArgs->pErrInfo); + else + { + /* + * Generic path. Start by writing the header for this object. + */ + rc = RTAsn1EncodeWriteHeader(pAsn1Core, pArgs->fFlags, pArgs->pfnWriter, pArgs->pvUser, pArgs->pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * If there is an enum function, call it to assemble the content. + * Otherwise ASSUME the pointer in the header points to the content. + */ + if ( pAsn1Core->pOps + && pAsn1Core->pOps->pfnEnum) + { + if (rc != VINF_ASN1_NOT_ENCODED) + rc = pAsn1Core->pOps->pfnEnum(pAsn1Core, rtAsn1EncodeWriteCallback, uDepth + 1, pArgs); + } + else if (pAsn1Core->cb && rc != VINF_ASN1_NOT_ENCODED) + { + Assert(!RTASN1CORE_IS_DUMMY(pAsn1Core)); + AssertPtrReturn(pAsn1Core->uData.pv, + RTErrInfoSetF(pArgs->pErrInfo, VERR_ASN1_INVALID_DATA_POINTER, + "Invalid uData pointer %p for no pfnEnum object with %#x bytes of content", + pAsn1Core->uData.pv, pAsn1Core->cb)); + rc = pArgs->pfnWriter(pAsn1Core->uData.pv, pAsn1Core->cb, pArgs->pvUser, pArgs->pErrInfo); + } + } + } + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + } + else + rc = VINF_SUCCESS; + return rc; +} + + +RTDECL(int) RTAsn1EncodeWrite(PCRTASN1CORE pRoot, uint32_t fFlags, FNRTASN1ENCODEWRITER pfnWriter, void *pvUser, + PRTERRINFO pErrInfo) +{ + AssertReturn((fFlags & RTASN1ENCODE_F_RULE_MASK) == RTASN1ENCODE_F_DER, VERR_INVALID_FLAGS); + + /* + * This is implemented as a recursive enumeration of the ASN.1 object structure. + */ + RTASN1ENCODEWRITEARGS Args; + Args.fFlags = fFlags; + Args.pfnWriter = pfnWriter; + Args.pvUser = pvUser; + Args.pErrInfo = pErrInfo; + return rtAsn1EncodeWriteCallback((PRTASN1CORE)pRoot, "root", 0, &Args); +} + + +static DECLCALLBACK(int) rtAsn1EncodeToBufferCallback(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo) +{ + RTASN1ENCODETOBUFARGS *pArgs = (RTASN1ENCODETOBUFARGS *)pvUser; + if (RT_LIKELY(pArgs->cbDst >= cbToWrite)) + { + memcpy(pArgs->pbDst, pvBuf, cbToWrite); + pArgs->cbDst -= cbToWrite; + pArgs->pbDst += cbToWrite; + return VINF_SUCCESS; + } + + /* + * Overflow. + */ + if (pArgs->cbDst) + { + memcpy(pArgs->pbDst, pvBuf, pArgs->cbDst); + pArgs->pbDst -= pArgs->cbDst; + pArgs->cbDst = 0; + } + RT_NOREF_PV(pErrInfo); + return VERR_BUFFER_OVERFLOW; +} + + +RTDECL(int) RTAsn1EncodeToBuffer(PCRTASN1CORE pRoot, uint32_t fFlags, void *pvBuf, size_t cbBuf, PRTERRINFO pErrInfo) +{ + RTASN1ENCODETOBUFARGS Args; + Args.pbDst = (uint8_t *)pvBuf; + Args.cbDst = cbBuf; + return RTAsn1EncodeWrite(pRoot, fFlags, rtAsn1EncodeToBufferCallback, &Args, pErrInfo); +} + + +RTDECL(int) RTAsn1EncodeQueryRawBits(PRTASN1CORE pRoot, const uint8_t **ppbRaw, uint32_t *pcbRaw, + void **ppvFree, PRTERRINFO pErrInfo) +{ + /* + * ASSUME that if we've got pointers here, they are valid... + */ + if ( pRoot->uData.pv + && !(pRoot->fFlags & RTASN1CORE_F_INDEFINITE_LENGTH) /* BER, not DER. */ + && (pRoot->fFlags & RTASN1CORE_F_DECODED_CONTENT) ) + { + /** @todo Check that it's DER encoding. */ + *ppbRaw = RTASN1CORE_GET_RAW_ASN1_PTR(pRoot); + *pcbRaw = RTASN1CORE_GET_RAW_ASN1_SIZE(pRoot); + *ppvFree = NULL; + return VINF_SUCCESS; + } + + /* + * Encode it into a temporary heap buffer. + */ + uint32_t cbEncoded = 0; + int rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, &cbEncoded, pErrInfo); + if (RT_SUCCESS(rc)) + { + void *pvEncoded = RTMemTmpAllocZ(cbEncoded); + if (pvEncoded) + { + rc = RTAsn1EncodeToBuffer(pRoot, RTASN1ENCODE_F_DER, pvEncoded, cbEncoded, pErrInfo); + if (RT_SUCCESS(rc)) + { + *ppvFree = pvEncoded; + *ppbRaw = (unsigned char *)pvEncoded; + *pcbRaw = cbEncoded; + return VINF_SUCCESS; + } + RTMemTmpFree(pvEncoded); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_NO_TMP_MEMORY, "RTMemTmpAllocZ(%u)", cbEncoded); + } + + *ppvFree = NULL; + *ppbRaw = NULL; + *pcbRaw = 0; + return rc; +} + diff --git a/src/VBox/Runtime/common/asn1/asn1-safer-allocator.cpp b/src/VBox/Runtime/common/asn1/asn1-safer-allocator.cpp new file mode 100644 index 00000000..602b2faa --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-safer-allocator.cpp @@ -0,0 +1,232 @@ +/* $Id: asn1-safer-allocator.cpp $ */ +/** @file + * IPRT - ASN.1, Safer Allocator, for sensitive data. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/memsafer.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + + +/** + * Aligns allocation sizes a little. + * + * @returns Aligned size. + * @param cb Requested size. + */ +static size_t rtAsn1SaferAllocator_AlignSize(size_t cb) +{ + if (cb >= 64) + return RT_ALIGN_Z(cb, 64); + if (cb >= 32) + return RT_ALIGN_Z(cb, 32); + if (cb >= 16) + return RT_ALIGN_Z(cb, 16); + return cb; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFree} */ +static DECLCALLBACK(void) rtAsn1SaferAllocator_Free(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, void *pv) +{ + RT_NOREF_PV(pThis); + RTMemSaferFree(pv, pAllocation->cbAllocated); + pAllocation->cbAllocated = 0; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnAlloc} */ +static DECLCALLBACK(int) rtAsn1SaferAllocator_Alloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, + void **ppv, size_t cb) +{ + size_t cbAlloc = rtAsn1SaferAllocator_AlignSize(cb); + void *pv = RTMemSaferAllocZ(cbAlloc); + if (pv) + { + *ppv = pv; + pAllocation->cbAllocated = (uint32_t)cbAlloc; + return VINF_SUCCESS; + } + RT_NOREF_PV(pThis); + return VERR_NO_MEMORY; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnRealloc} */ +static DECLCALLBACK(int) rtAsn1SaferAllocator_Realloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, + void *pvOld, void **ppvNew, size_t cbNew) +{ + Assert(pvOld); + Assert(cbNew); + size_t cbAlloc = rtAsn1SaferAllocator_AlignSize(cbNew); + void *pv = RTMemSaferReallocZ(pAllocation->cbAllocated, pvOld, cbAlloc); + if (pv) + { + *ppvNew = pv; + pAllocation->cbAllocated = (uint32_t)cbAlloc; + return VINF_SUCCESS; + } + RT_NOREF_PV(pThis); + return VERR_NO_MEMORY; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFreeArray} */ +static DECLCALLBACK(void) rtAsn1SaferAllocator_FreeArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void **papvArray) +{ + RT_NOREF_PV(pThis); + Assert(papvArray); + Assert(pAllocation->cbEntry); + + uint32_t i = pAllocation->cEntriesAllocated; + while (i-- > 0) + RTMemSaferFree(papvArray[i], pAllocation->cbEntry); + RTMemFree(papvArray); + + pAllocation->cEntriesAllocated = 0; + pAllocation->cPointersAllocated = 0; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnGrowArray} */ +static DECLCALLBACK(int) rtAsn1SaferAllocator_GrowArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void ***ppapvArray, uint32_t cMinEntries) +{ + RT_NOREF_PV(pThis); + + /* + * Resize the pointer array. We do chunks of 64 bytes for now. + */ + void **papvArray = *ppapvArray; + uint32_t cPointers = RT_ALIGN_32(cMinEntries, 64 / sizeof(void *)); + if (cPointers > pAllocation->cPointersAllocated) + { + void *pvPointers = RTMemRealloc(papvArray, cPointers * sizeof(void *)); + if (pvPointers) + { /* likely */ } + else if (cMinEntries > pAllocation->cPointersAllocated) + { + cPointers = cMinEntries; + pvPointers = RTMemRealloc(*ppapvArray, cPointers * sizeof(void *)); + if (!pvPointers) + return VERR_NO_MEMORY; + } + else + { + cPointers = pAllocation->cPointersAllocated; + pvPointers = papvArray; + } + + *ppapvArray = papvArray = (void **)pvPointers; + RT_BZERO(&papvArray[pAllocation->cPointersAllocated], (cPointers - pAllocation->cPointersAllocated) * sizeof(void *)); + pAllocation->cPointersAllocated = cPointers; + } + + /* + * Add more entries. Do multiple as the array grows. + * + * Note! We could possibly optimize this by allocating slabs of entries and + * slice them up. However, keep things as simple as possible for now. + */ + uint32_t cEntries = cMinEntries; + if (cEntries > 2) + { + if (cEntries > 8) + cEntries = RT_ALIGN_32(cEntries, 4); + else + cEntries = RT_ALIGN_32(cEntries, 2); + cEntries = RT_MIN(cEntries, cPointers); + Assert(cEntries >= cMinEntries); + } + Assert(cEntries <= pAllocation->cPointersAllocated); + + while (pAllocation->cEntriesAllocated < cEntries) + { + void *pv; + papvArray[pAllocation->cEntriesAllocated] = pv = RTMemSaferAllocZ(pAllocation->cbEntry); + if (pv) + pAllocation->cEntriesAllocated++; + else if (pAllocation->cEntriesAllocated >= cMinEntries) + break; + else + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnShrinkArray} */ +static DECLCALLBACK(void) rtAsn1SaferAllocator_ShrinkArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation, + void ***ppapvArray, uint32_t cNew, uint32_t cCurrent) +{ + RT_NOREF_PV(pThis); + + /* + * For now we only zero the entries being removed. + */ + void **papvArray = *ppapvArray; + while (cNew < cCurrent) + { + RTMemWipeThoroughly(papvArray[cNew], pAllocation->cbEntry, 3); + RT_BZERO(papvArray[cNew], pAllocation->cbEntry); + cNew++; + } +} + + + +/** The Safer ASN.1 allocator. */ +#if 1 || !defined(IN_RING3) || defined(DOXYGEN_RUNNING) +RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1SaferAllocator = +#else +RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1SaferAllocatorDisabled = +#endif +{ + rtAsn1SaferAllocator_Free, + rtAsn1SaferAllocator_Alloc, + rtAsn1SaferAllocator_Realloc, + rtAsn1SaferAllocator_FreeArray, + rtAsn1SaferAllocator_GrowArray, + rtAsn1SaferAllocator_ShrinkArray +}; + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-decode.cpp new file mode 100644 index 00000000..994173f1 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-decode.cpp @@ -0,0 +1,141 @@ +/* $Id: asn1-ut-bitstring-decode.cpp $ */ +/** @file + * IPRT - ASN.1, BIT STRING Type, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + + +RTDECL(int) RTAsn1BitString_DecodeAsn1Ex(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t cMaxBits, PRTASN1BITSTRING pThis, + const char *pszErrorTag) +{ + pThis->cBits = 0; + pThis->cMaxBits = cMaxBits; + pThis->uBits.pv = NULL; + pThis->pEncapsulated = NULL; + RTAsn1CursorInitAllocation(pCursor, &pThis->EncapsulatedAllocation); + + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlagsString(pCursor, &pThis->Asn1Core, ASN1_TAG_BIT_STRING, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + fFlags, pszErrorTag, "BIT STRING"); + if (RT_SUCCESS(rc)) + { + if (!(pThis->Asn1Core.fClass & ASN1_TAGFLAG_CONSTRUCTED)) + { + if ( ( cMaxBits == UINT32_MAX + || RT_ALIGN(cMaxBits, 8) / 8 + 1 >= pThis->Asn1Core.cb) + && pThis->Asn1Core.cb > 0) + { + uint8_t cUnusedBits = pThis->Asn1Core.cb > 0 ? *pThis->Asn1Core.uData.pu8 : 0; + if (pThis->Asn1Core.cb < 2) + { + /* Not bits present. */ + if (cUnusedBits == 0) + { + pThis->cBits = 0; + pThis->uBits.pv = NULL; + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1BitString_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + return VINF_SUCCESS; + } + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BITSTRING_ENCODING, + "%s: Bad unused bit count: %#x (cb=%#x)", + pszErrorTag, cUnusedBits, pThis->Asn1Core.cb); + } + else if (cUnusedBits < 8) + { + pThis->cBits = (pThis->Asn1Core.cb - 1) * 8; + pThis->cBits -= cUnusedBits; + pThis->uBits.pu8 = pThis->Asn1Core.uData.pu8 + 1; + if ( !(pCursor->fFlags & (RTASN1CURSOR_FLAGS_DER | RTASN1CURSOR_FLAGS_CER)) + || cUnusedBits == 0 + || !( pThis->uBits.pu8[pThis->Asn1Core.cb - 2] & (((uint8_t)1 << cUnusedBits) - (uint8_t)1) ) ) + { + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1BitString_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + return VINF_SUCCESS; + } + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BITSTRING_ENCODING, + "%s: Unused bits shall be zero in DER/CER mode: last byte=%#x cUnused=%#x", + pszErrorTag, pThis->uBits.pu8[pThis->cBits / 8], cUnusedBits); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BITSTRING_ENCODING, + "%s: Bad unused bit count: %#x (cb=%#x)", + pszErrorTag, cUnusedBits, pThis->Asn1Core.cb); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BITSTRING_ENCODING, + "%s: Size mismatch: cb=%#x, expected %#x (cMaxBits=%#x)", + pszErrorTag, pThis->Asn1Core.cb, RT_ALIGN(cMaxBits, 8) / 8 + 1, cMaxBits); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CONSTRUCTED_STRING_NOT_IMPL, + "%s: Constructed BIT STRING not implemented.", pszErrorTag); + } + } + RT_ZERO(*pThis); + return rc; +} + + +RTDECL(int) RTAsn1BitString_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1BITSTRING pThis, const char *pszErrorTag) +{ + return RTAsn1BitString_DecodeAsn1Ex(pCursor, fFlags, UINT32_MAX, pThis, pszErrorTag); +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-bitstring-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-template.h new file mode 100644 index 00000000..17375f86 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-template.h @@ -0,0 +1,55 @@ +/* $Id: asn1-ut-bitstring-template.h $ */ +/** @file + * IPRT - ASN.1, Bit String Type, Collection Type Template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +#define RTASN1TMPL_TYPE RTASN1SEQOFBITSTRINGS +#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfBitStrings +#define RTASN1TMPL_INT_NAME rtAsn1SeqOfBitStrings +RTASN1TMPL_SEQ_OF(RTASN1BITSTRING, RTAsn1BitString); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFBITSTRINGS +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfBitStrings +#define RTASN1TMPL_INT_NAME rtAsn1SetOfBitStrings +RTASN1TMPL_SET_OF(RTASN1BITSTRING, RTAsn1BitString); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp new file mode 100644 index 00000000..80499ab5 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp @@ -0,0 +1,548 @@ +/* $Id: asn1-ut-bitstring.cpp $ */ +/** @file + * IPRT - ASN.1, Bit String Type. + * + * @remarks This file should remain very similar to asn1-ut-octetstring.cpp. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/alloca.h> +#include <iprt/bignum.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RTASN1BITSTRINGWRITERCTX +{ + /** Pointer to the output buffer. */ + uint8_t *pbBuf; + /** The current buffer offset. */ + uint32_t offBuf; + /** The size of the buffer. */ + uint32_t cbBuf; +} RTASN1BITSTRINGWRITERCTX; + + +/** @callback_method_impl{FNRTASN1ENCODEWRITER, + * Used to refresh the content of octet and bit strings. } */ +static DECLCALLBACK(int) rtAsn1BitStringEncodeWriter(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo) +{ + RTASN1BITSTRINGWRITERCTX *pCtx = (RTASN1BITSTRINGWRITERCTX *)pvUser; + AssertReturn(cbToWrite <= pCtx->cbBuf - pCtx->offBuf, + RTErrInfoSetF(pErrInfo, VERR_BUFFER_OVERFLOW, + "cbToWrite=%#x offBuf=%#x cbBuf=%#x", cbToWrite, pCtx->cbBuf, pCtx->offBuf)); + memcpy(&pCtx->pbBuf[pCtx->offBuf], pvBuf, cbToWrite); + pCtx->offBuf += (uint32_t)cbToWrite; + return VINF_SUCCESS; +} + + +/** @callback_method_impl{FNRTASN1ENCODEWRITER, + * Used to compare the encoded raw content of an octet or bit string with the + * encapsulated object. } */ +static DECLCALLBACK(int) rtAsn1BitStringEncodeCompare(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo) +{ + RTASN1BITSTRINGWRITERCTX *pCtx = (RTASN1BITSTRINGWRITERCTX *)pvUser; + AssertReturn(cbToWrite <= pCtx->cbBuf - pCtx->offBuf, VERR_BUFFER_OVERFLOW); + if (memcmp(&pCtx->pbBuf[pCtx->offBuf], pvBuf, cbToWrite) != 0) + return VERR_NOT_EQUAL; + pCtx->offBuf += (uint32_t)cbToWrite; + RT_NOREF_PV(pErrInfo); + return VINF_SUCCESS; +} + + + +/* + * ASN.1 BIT STRING - Special Methods. + */ + +RTDECL(uint64_t) RTAsn1BitString_GetAsUInt64(PCRTASN1BITSTRING pThis) +{ + /* + * Extract the first 64 bits in host order. + */ + uint8_t const *pb = pThis->uBits.pu8; + uint64_t uRet = 0; + uint32_t cShift = 0; + uint32_t cBits = RT_MIN(pThis->cBits, 64); + while (cBits > 0) + { + uint8_t b = *pb++; +#if 1 /* We don't have a bit-order constant... */ + b = ((b & 0x01) << 7) + | ((b & 0x02) << 5) + | ((b & 0x04) << 3) + | ((b & 0x08) << 1) + | ((b & 0x10) >> 1) + | ((b & 0x20) >> 3) + | ((b & 0x40) >> 5) + | ((b & 0x80) >> 7); +#endif + if (cBits < 8) + { + b &= RT_BIT_32(cBits) - 1; + uRet |= (uint64_t)b << cShift; + break; + } + uRet |= (uint64_t)b << cShift; + cShift += 8; + cBits -= 8; + } + + return uRet; +} + + +RTDECL(int) RTAsn1BitString_RefreshContent(PRTASN1BITSTRING pThis, uint32_t fFlags, + PCRTASN1ALLOCATORVTABLE pAllocator, PRTERRINFO pErrInfo) +{ + AssertReturn(pThis->pEncapsulated, VERR_INVALID_STATE); + + uint32_t cbEncoded; + int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, pErrInfo); + if (RT_SUCCESS(rc)) + { + pThis->Asn1Core.cb = 1 + cbEncoded; + pThis->cBits = cbEncoded * 8; + AssertReturn(pThis->cBits / 8 == cbEncoded, RTErrInfoSetF(pErrInfo, VERR_TOO_MUCH_DATA, "cbEncoded=%#x", cbEncoded)); + + rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, cbEncoded + 1, pAllocator); + if (RT_SUCCESS(rc)) + { + pThis->uBits.pu8 = pThis->Asn1Core.uData.pu8 + 1; + + /* Initialize the writer context and write the first byte concerning unused bits. */ + RTASN1BITSTRINGWRITERCTX Ctx; + Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8; + Ctx.cbBuf = cbEncoded + 1; + Ctx.offBuf = 1; + *Ctx.pbBuf = 0; + + rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1BitStringEncodeWriter, &Ctx, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (Ctx.offBuf == cbEncoded + 1) + return VINF_SUCCESS; + + rc = RTErrInfoSetF(pErrInfo, rc, "Expected %#x + 1 bytes, got %#x", cbEncoded, Ctx.offBuf); + } + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "Error allocating %#x + 1 bytes for storing content\n", cbEncoded); + } + return rc; +} + + +RTDECL(bool) RTAsn1BitString_AreContentBitsValid(PCRTASN1BITSTRING pThis, uint32_t fFlags) +{ + if (pThis->pEncapsulated) + { + if (pThis->cBits & 7) + return false; + + /* Check the encoded length of the bits. */ + uint32_t cbEncoded; + int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, NULL); + if (RT_FAILURE(rc)) + return false; + if (pThis->Asn1Core.cb != 1 + cbEncoded) + return false; + + /* Check the encoded bits, if there are any. */ + if (cbEncoded) + { + if (!pThis->Asn1Core.uData.pv) + return false; + + /* Check the first byte, the unused bit count. */ + if (*pThis->Asn1Core.uData.pu8 != 0) + return false; + + /* Check the other bytes. */ + RTASN1BITSTRINGWRITERCTX Ctx; + Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8; + Ctx.cbBuf = cbEncoded + 1; + Ctx.offBuf = 1; + rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1BitStringEncodeCompare, &Ctx, NULL); + if (RT_FAILURE(rc)) + return false; + } + } + return true; +} + + + + +/* + * ASN.1 BIT STRING - Standard Methods. + */ + +/** @interface_method_impl{FNRTASN1COREVTENCODEPREP} */ +static DECLCALLBACK(int) RTAsn1BitString_EncodePrep(PRTASN1CORE pThisCore, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + PRTASN1BITSTRING pThis = (PRTASN1BITSTRING)pThisCore; + if (!pThis->pEncapsulated) + { + Assert(pThis->cBits == 0 || pThis->Asn1Core.uData.pv); + return VINF_SUCCESS; + } + + /* Figure out the size of the encapsulated content. */ + uint32_t cbEncoded; + int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* Free the bytes if they don't match up. */ + if (pThis->Asn1Core.uData.pv) + { + bool fMustFree = pThis->Asn1Core.cb != 1 + cbEncoded || (pThis->cBits & 7); + if (!fMustFree) + { + RTASN1BITSTRINGWRITERCTX Ctx; + Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8; + Ctx.cbBuf = 1 + cbEncoded; + Ctx.offBuf = 1; + fMustFree = *Ctx.pbBuf != 0; + if (!fMustFree) + { + rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1BitStringEncodeCompare, &Ctx, NULL); + fMustFree = RT_FAILURE_NP(rc); + } + } + if (fMustFree) + { + pThis->uBits.pv = NULL; + RTAsn1ContentFree(&pThis->Asn1Core); + } + } + pThis->Asn1Core.cb = 1 + cbEncoded; + pThis->cBits = cbEncoded * 8; + + rc = RTAsn1EncodeRecalcHdrSize(&pThis->Asn1Core, fFlags, pErrInfo); + } + return rc; +} + + +/** @interface_method_impl{FNRTASN1COREVTENCODEWRITE} */ +static DECLCALLBACK(int) RTAsn1BitString_EncodeWrite(PRTASN1CORE pThisCore, uint32_t fFlags, PFNRTASN1ENCODEWRITER pfnWriter, + void *pvUser, PRTERRINFO pErrInfo) +{ + PRTASN1BITSTRING pThis = (PRTASN1BITSTRING)pThisCore; + + AssertReturn(RT_ALIGN(pThis->cBits, 8) / 8 + 1 == pThis->Asn1Core.cb, VERR_INTERNAL_ERROR_3); + + /* + * First the header. + */ + int rc = RTAsn1EncodeWriteHeader(&pThis->Asn1Core, fFlags, pfnWriter, pvUser, pErrInfo); + if (RT_SUCCESS(rc) && rc != VINF_ASN1_NOT_ENCODED) + { + /* + * The content starts with an unused bit count. Calculate it in case we + * need to write it out. + */ + uint8_t cUnusedBits = 0; + if ((pThis->cBits & 7) != 0) + cUnusedBits = 8 - (pThis->cBits & 7); + + /* + * If nothing is encapsulated, the core points to the content (if we have any). + */ + if (!pThis->pEncapsulated) + { + if (pThis->cBits > 0) + { + Assert(pThis->Asn1Core.uData.pu8[0] == cUnusedBits); + rc = pfnWriter(pThis->Asn1Core.uData.pu8, pThis->Asn1Core.cb, pvUser, pErrInfo); + } + else + rc = pfnWriter(&cUnusedBits, sizeof(cUnusedBits), pvUser, pErrInfo); + } + /* + * Write the unused bit count and then call upon the encapsulated + * content to serialize itself. + */ + else + { + rc = pfnWriter(&cUnusedBits, sizeof(cUnusedBits), pvUser, pErrInfo); + if (RT_SUCCESS(rc)) + rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, pfnWriter, pvUser, pErrInfo); + } + } + return rc; +} + + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1BitString_Vtable = +{ + "RTAsn1BitString", + sizeof(RTASN1BITSTRING), + ASN1_TAG_BIT_STRING, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + 0, + (PFNRTASN1COREVTDTOR)RTAsn1BitString_Delete, + (PFNRTASN1COREVTENUM)RTAsn1BitString_Enum, + (PFNRTASN1COREVTCLONE)RTAsn1BitString_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1BitString_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1BitString_CheckSanity, + RTAsn1BitString_EncodePrep, + RTAsn1BitString_EncodeWrite +}; + + +RTDECL(int) RTAsn1BitString_Init(PRTASN1BITSTRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_ZERO(*pThis); + + RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_BIT_STRING, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1BitString_Vtable, RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + /*pThis->cBits = 0; + pThis->cMaxBits = 0; + pThis->uBits.pv = NULL; + pThis->pEncapsulated = NULL; */ + RTAsn1MemInitAllocation(&pThis->EncapsulatedAllocation, pAllocator); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1BitString_InitWithData(PRTASN1BITSTRING pThis, void const *pvSrc, uint32_t cSrcBits, + PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RTAsn1BitString_Init(pThis, pAllocator); + Assert(pThis->pEncapsulated == NULL); + + uint32_t cbToCopy = (cSrcBits + 7) / 8; + int rc = RTAsn1ContentAllocZ(&pThis->Asn1Core, cbToCopy + 1, pAllocator); + if (RT_SUCCESS(rc)) + { + pThis->cBits = cSrcBits; + uint8_t *pbDst = (uint8_t *)pThis->Asn1Core.uData.pu8; + pThis->uBits.pv = pbDst + 1; + *pbDst = (cSrcBits & 7) != 0 ? 8 - (cSrcBits & 7) : 0; /* unused bits */ + memcpy(pbDst + 1, pvSrc, cbToCopy); + } + return rc; +} + + +RTDECL(int) RTAsn1BitString_Clone(PRTASN1BITSTRING pThis, PCRTASN1BITSTRING pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); + + RT_ZERO(*pThis); + if (RTAsn1BitString_IsPresent(pSrc)) + { + AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1BitString_Vtable, VERR_INTERNAL_ERROR_3); + + int rc; + if (!pSrc->pEncapsulated) + rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator); + else + rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); + if (RT_FAILURE(rc)) + return rc; + + RTAsn1MemInitAllocation(&pThis->EncapsulatedAllocation, pAllocator); + pThis->cBits = pSrc->cBits; + pThis->cMaxBits = pSrc->cMaxBits; + if (!pSrc->pEncapsulated) + pThis->uBits.pv = pThis->Asn1Core.uData.pu8 ? pThis->Asn1Core.uData.pu8 + 1 : NULL; + else + { + PCRTASN1COREVTABLE pOps = pSrc->pEncapsulated->pOps; + Assert(!pOps || pOps->pfnClone); + if (pOps && pOps->pfnClone) + { + /* We can clone the decoded encapsulated object. */ + rc = RTAsn1MemAllocZ(&pThis->EncapsulatedAllocation, (void **)&pThis->pEncapsulated, pOps->cbStruct); + if (RT_SUCCESS(rc)) + { + rc = pOps->pfnClone(pThis->pEncapsulated, pSrc->pEncapsulated, pAllocator); + if (RT_FAILURE(rc)) + RTAsn1MemFree(&pThis->EncapsulatedAllocation, pThis->pEncapsulated); + } + } + else + { + /* Borrow the encapsulated pointer and use RTAsn1BitString_RefreshContent + to get an accurate copy of the bytes. */ + pThis->pEncapsulated = pSrc->pEncapsulated; + rc = RTAsn1BitString_RefreshContent(pThis, RTASN1ENCODE_F_DER, pAllocator, NULL); + pThis->pEncapsulated = NULL; + } + if (RT_FAILURE(rc)) + { + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + return rc; + } + } + } + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1BitString_Delete(PRTASN1BITSTRING pThis) +{ + if ( pThis + && RTAsn1BitString_IsPresent(pThis)) + { + Assert(pThis->Asn1Core.pOps == &g_RTAsn1BitString_Vtable); + + /* Destroy the encapsulated object. */ + if (pThis->pEncapsulated) + { + RTAsn1VtDelete(pThis->pEncapsulated); + if (pThis->EncapsulatedAllocation.cbAllocated) + RTAsn1MemFree(&pThis->EncapsulatedAllocation, pThis->pEncapsulated); + } + + /* Delete content and wipe the content. */ + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1BitString_Enum(PRTASN1BITSTRING pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + Assert(pThis && (!RTAsn1BitString_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1BitString_Vtable)); + + /* Enumerate the encapsulated object if present. */ + if (pThis->pEncapsulated) + return pfnCallback(pThis->pEncapsulated, "Encapsulated", uDepth + 1, pvUser); + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1BitString_Compare(PCRTASN1BITSTRING pLeft, PCRTASN1BITSTRING pRight) +{ + Assert(pLeft && (!RTAsn1BitString_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1BitString_Vtable)); + Assert(pRight && (!RTAsn1BitString_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1BitString_Vtable)); + + int iDiff; + if (RTAsn1BitString_IsPresent(pLeft)) + { + if (RTAsn1BitString_IsPresent(pRight)) + { + /* Since it's really hard to tell whether encapsulated objects have + been modified or not, we might have to refresh both objects + while doing this compare. We'll try our best to avoid it though. */ + if (pLeft->pEncapsulated || pRight->pEncapsulated) + { + if ( pLeft->pEncapsulated + && pRight->pEncapsulated + && pLeft->pEncapsulated->pOps == pRight->pEncapsulated->pOps) + iDiff = pLeft->pEncapsulated->pOps->pfnCompare(pLeft->pEncapsulated, pRight->pEncapsulated); + else + { + /* No direct comparison of encapsulated objects possible, + make sure we've got the rigth bytes then. */ + if ( pLeft->pEncapsulated + && !RTAsn1BitString_AreContentBitsValid(pLeft, RTASN1ENCODE_F_DER)) + { + int rc = RTAsn1BitString_RefreshContent((PRTASN1BITSTRING)pLeft, RTASN1ENCODE_F_DER, + pLeft->EncapsulatedAllocation.pAllocator, NULL); + AssertRC(rc); + } + + if ( pRight->pEncapsulated + && !RTAsn1BitString_AreContentBitsValid(pRight, RTASN1ENCODE_F_DER)) + { + int rc = RTAsn1BitString_RefreshContent((PRTASN1BITSTRING)pRight, RTASN1ENCODE_F_DER, + pRight->EncapsulatedAllocation.pAllocator, NULL); + AssertRC(rc); + } + + /* Compare the content bytes. */ + iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/); + } + } + /* + * No encapsulated object, just compare the raw content bytes. + */ + else + iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/); + } + else + iDiff = -1; + } + else + iDiff = 0 - (int)RTAsn1BitString_IsPresent(pRight); + return iDiff; +} + + +RTDECL(int) RTAsn1BitString_CheckSanity(PCRTASN1BITSTRING pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + if (RT_UNLIKELY(!RTAsn1BitString_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (BIT STRING).", pszErrorTag); + + if (pThis->cBits > pThis->cMaxBits) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_BITSTRING_OUT_OF_BOUNDS, "%s: Exceeding max bits: cBits=%u cMaxBits=%u.", + pszErrorTag, pThis->cBits, pThis->cMaxBits); + + if (pThis->pEncapsulated) + return pThis->pEncapsulated->pOps->pfnCheckSanity(pThis->pEncapsulated, fFlags & RTASN1_CHECK_SANITY_F_COMMON_MASK, + pErrInfo, pszErrorTag); + return VINF_SUCCESS; +} + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-bitstring-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-core.h> +#include <iprt/asn1-generator-init.h> +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-boolean-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-boolean-decode.cpp new file mode 100644 index 00000000..a8db498f --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-boolean-decode.cpp @@ -0,0 +1,93 @@ +/* $Id: asn1-ut-boolean-decode.cpp $ */ +/** @file + * IPRT - ASN.1, BOOLEAN Type, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + + +RTDECL(int) RTAsn1Boolean_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1BOOLEAN pThis, const char *pszErrorTag) +{ + pThis->fValue = 0; + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_BOOLEAN, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, fFlags, pszErrorTag, "BOOLEAN"); + if (RT_SUCCESS(rc)) + { + if (pThis->Asn1Core.cb == 1) + { + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + pThis->Asn1Core.pOps = &g_RTAsn1Boolean_Vtable; + + pThis->fValue = *pThis->Asn1Core.uData.pu8 != 0; + if ( *pThis->Asn1Core.uData.pu8 == 0 + || *pThis->Asn1Core.uData.pu8 == 0xff + || !(pCursor->fFlags & (RTASN1CURSOR_FLAGS_DER | RTASN1CURSOR_FLAGS_CER)) ) + return VINF_SUCCESS; + + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BOOLEAN_ENCODING, + "%s: Invalid CER/DER boolean value: %#x, valid: 0, 0xff", + pszErrorTag, *pThis->Asn1Core.uData.pu8); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BOOLEAN_ENCODING, "%s: Invalid boolean length, exepcted 1: %#x", + pszErrorTag, pThis->Asn1Core.cb); + } + } + RT_ZERO(*pThis); + return rc; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-boolean-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-boolean-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-boolean-template.h new file mode 100644 index 00000000..39ef92e5 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-boolean-template.h @@ -0,0 +1,55 @@ +/* $Id: asn1-ut-boolean-template.h $ */ +/** @file + * IPRT - ASN.1, BOOLEAN Type, Collection Type Template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +#define RTASN1TMPL_TYPE RTASN1SEQOFBOOLEANS +#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfBooleans +#define RTASN1TMPL_INT_NAME rtAsn1SeqOfBooleans +RTASN1TMPL_SEQ_OF(RTASN1BOOLEAN, RTAsn1Boolean); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFBOOLEANS +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfBooleans +#define RTASN1TMPL_INT_NAME rtAsn1SetOfBooleans +RTASN1TMPL_SET_OF(RTASN1BOOLEAN, RTAsn1Boolean); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp new file mode 100644 index 00000000..7b0485e5 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp @@ -0,0 +1,218 @@ +/* $Id: asn1-ut-boolean.cpp $ */ +/** @file + * IPRT - ASN.1, BOOLEAN Type. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/bignum.h> +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The false value (DER & CER). */ +static const uint8_t g_bFalse = 0; +/** The true value (DER & CER). */ +static const uint8_t g_bTrue = 0xff; + + +/* + * ASN.1 BOOLEAN - Special Methods. + */ + +RTDECL(int) RTAsn1Boolean_InitDefault(PRTASN1BOOLEAN pThis, bool fValue, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_NOREF_PV(pAllocator); + RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_BOOLEAN, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1Boolean_Vtable, RTASN1CORE_F_DEFAULT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + pThis->fValue = fValue; + pThis->Asn1Core.uData.pv = (void *)(fValue ? &g_bTrue : &g_bFalse); + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Boolean_Set(PRTASN1BOOLEAN pThis, bool fValue) +{ + /* Since we don't need an allocator, let's automatically initialize the struct. */ + if (!RTAsn1Boolean_IsPresent(pThis)) + RTAsn1Boolean_Init(pThis, NULL); + else + RTAsn1ContentFree(&pThis->Asn1Core); + pThis->fValue = fValue; + pThis->Asn1Core.uData.pv = (void *)(fValue ? &g_bTrue : &g_bFalse); + pThis->Asn1Core.cb = 1; + pThis->Asn1Core.fFlags &= ~RTASN1CORE_F_DEFAULT; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRESENT; + return VINF_SUCCESS; +} + + + +/* + * ASN.1 BOOLEAN - Standard Methods. + */ + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Boolean_Vtable = +{ + "RTAsn1Boolean", + sizeof(RTASN1BOOLEAN), + ASN1_TAG_BOOLEAN, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + 0, + (PFNRTASN1COREVTDTOR)RTAsn1Boolean_Delete, + NULL, + (PFNRTASN1COREVTCLONE)RTAsn1Boolean_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1Boolean_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1Boolean_CheckSanity, + NULL, + NULL +}; + + +RTDECL(int) RTAsn1Boolean_Init(PRTASN1BOOLEAN pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_NOREF_PV(pAllocator); + RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_BOOLEAN, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1Boolean_Vtable, RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + pThis->fValue = true; + pThis->Asn1Core.cb = 1; + pThis->Asn1Core.uData.pv = (void *)&g_bTrue; + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Boolean_Clone(PRTASN1BOOLEAN pThis, PCRTASN1BOOLEAN pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); + RT_ZERO(*pThis); + if (RTAsn1Boolean_IsPresent(pSrc)) + { + AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable, VERR_INTERNAL_ERROR_3); + AssertReturn(pSrc->Asn1Core.cb <= 1, VERR_INTERNAL_ERROR_4); + + int rc; + if ( pSrc->Asn1Core.cb == 1 + && pSrc->Asn1Core.uData.pu8[0] != 0x00 + && pSrc->Asn1Core.uData.pu8[0] != 0xff) + { + /* DER/CER incompatible value must be copied as-is. */ + rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator); + if (RT_FAILURE(rc)) + return rc; + } + else + { + /* No value or one of the standard values. */ + rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); + if (RT_FAILURE(rc)) + return rc; + pThis->Asn1Core.uData.pv = (void *)(pSrc->fValue ? &g_bTrue : &g_bFalse); + } + pThis->fValue = pSrc->fValue; + } + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1Boolean_Delete(PRTASN1BOOLEAN pThis) +{ + if ( pThis + && RTAsn1Boolean_IsPresent(pThis)) + { + Assert(pThis->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable); + Assert(pThis->Asn1Core.cb <= 1); + + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1Boolean_Enum(PRTASN1BOOLEAN pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + Assert(pThis && (!RTAsn1Boolean_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable)); + RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); + + /* No children to enumerate. */ + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Boolean_Compare(PCRTASN1BOOLEAN pLeft, PCRTASN1BOOLEAN pRight) +{ + Assert(pLeft && (!RTAsn1Boolean_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable)); + Assert(pRight && (!RTAsn1Boolean_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable)); + + int iDiff; + if (RTAsn1Boolean_IsPresent(pLeft)) + { + if (RTAsn1Boolean_IsPresent(pRight)) + iDiff = (int)pLeft->fValue - (int)pRight->fValue; + else + iDiff = -1; + } + else + iDiff = 0 - (int)RTAsn1Boolean_IsPresent(pRight); + return iDiff; +} + + +RTDECL(int) RTAsn1Boolean_CheckSanity(PCRTASN1BOOLEAN pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + if (RT_UNLIKELY(!RTAsn1Boolean_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (BOOLEAN).", pszErrorTag); + RT_NOREF_PV(fFlags); + return VINF_SUCCESS; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-boolean-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-core.h> +#include <iprt/asn1-generator-init.h> +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-core-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-core-decode.cpp new file mode 100644 index 00000000..3015ab89 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-core-decode.cpp @@ -0,0 +1,69 @@ +/* $Id: asn1-ut-core-decode.cpp $ */ +/** @file + * IPRT - ASN.1, Generic Core Type, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> + + +RTDECL(int) RTAsn1Core_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1CORE pThis, const char *pszErrorTag) +{ + int rc = RTAsn1CursorReadHdr(pCursor, pThis, pszErrorTag); + if (RT_SUCCESS(rc)) + { + RTAsn1CursorSkip(pCursor, pThis->cb); + pThis->pOps = &g_RTAsn1Core_Vtable; + return VINF_SUCCESS; + } + RT_NOREF_PV(fFlags); + RT_ZERO(*pThis); + return rc; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-core-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-core-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-core-template.h new file mode 100644 index 00000000..10a9b995 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-core-template.h @@ -0,0 +1,55 @@ +/* $Id: asn1-ut-core-template.h $ */ +/** @file + * IPRT - ASN.1, Generic Core Type, Collection Type Template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +#define RTASN1TMPL_TYPE RTASN1SEQOFCORES +#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfCores +#define RTASN1TMPL_INT_NAME rtAsn1SeqOfCores +RTASN1TMPL_SEQ_OF(RTASN1CORE, RTAsn1Core); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFCORES +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfCores +#define RTASN1TMPL_INT_NAME rtAsn1SetOfCores +RTASN1TMPL_SET_OF(RTASN1CORE, RTAsn1Core); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-core.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-core.cpp new file mode 100644 index 00000000..b9ff385f --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-core.cpp @@ -0,0 +1,331 @@ +/* $Id: asn1-ut-core.cpp $ */ +/** @file + * IPRT - ASN.1, Generic Core Type. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/alloca.h> +#include <iprt/bignum.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include <iprt/formats/asn1.h> + + +/* + * ASN.1 Core - Special methods (for all applications of RTASN1CORE). + */ + +RTDECL(int) RTAsn1Core_SetTagAndFlags(PRTASN1CORE pAsn1Core, uint32_t uTag, uint8_t fClass) +{ + if (!(pAsn1Core->fFlags & RTASN1CORE_F_TAG_IMPLICIT)) + { + pAsn1Core->fRealClass = pAsn1Core->fClass; + pAsn1Core->uRealTag = pAsn1Core->uTag; + Assert(pAsn1Core->uRealTag == pAsn1Core->uTag); + pAsn1Core->fFlags |= RTASN1CORE_F_TAG_IMPLICIT; + } + pAsn1Core->uTag = uTag; + pAsn1Core->fClass = fClass; + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Core_ChangeTag(PRTASN1CORE pAsn1Core, uint32_t uTag) +{ + if (!(pAsn1Core->fFlags & RTASN1CORE_F_TAG_IMPLICIT)) + pAsn1Core->uTag = uTag; + pAsn1Core->uRealTag = uTag; + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1Core_ResetImplict(PRTASN1CORE pThis) +{ + AssertPtr(pThis); + if (pThis->fFlags & RTASN1CORE_F_TAG_IMPLICIT) + { + pThis->fFlags &= ~RTASN1CORE_F_TAG_IMPLICIT; + pThis->uTag = pThis->uRealTag; + pThis->fClass = pThis->fRealClass; + } +} + + +RTDECL(int) RTAsn1Core_InitEx(PRTASN1CORE pAsn1Core, uint32_t uTag, uint8_t fClass, PCRTASN1COREVTABLE pOps, uint32_t fFlags) +{ + pAsn1Core->uTag = uTag; + pAsn1Core->fClass = fClass; + pAsn1Core->uRealTag = uTag; + pAsn1Core->fRealClass = fClass; + pAsn1Core->cbHdr = 0; + pAsn1Core->cb = 0; + pAsn1Core->fFlags = fFlags; + pAsn1Core->uData.pv = NULL; + pAsn1Core->pOps = pOps; + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Core_InitDefault(PRTASN1CORE pAsn1Core, uint32_t uTag, uint8_t fClass) +{ + return RTAsn1Core_InitEx(pAsn1Core, uTag, fClass, NULL, RTASN1CORE_F_DEFAULT); +} + + +static int rtAsn1Core_CloneEx(PRTASN1CORE pThis, PCRTASN1CORE pSrc, PCRTASN1ALLOCATORVTABLE pAllocator, bool fCopyContent) +{ + Assert(RTASN1CORE_IS_PRESENT(pSrc)); + pThis->uTag = pSrc->uTag; + pThis->fClass = pSrc->fClass; + pThis->uRealTag = pSrc->uRealTag; + pThis->fRealClass = pSrc->fRealClass; + pThis->cbHdr = pSrc->cbHdr; + pThis->fFlags = pSrc->fFlags & ~(RTASN1CORE_F_ALLOCATED_CONTENT | RTASN1CORE_F_DECODED_CONTENT); + pThis->pOps = pSrc->pOps; + pThis->cb = 0; + pThis->uData.pv = NULL; + if (pSrc->cb) + { + if (!fCopyContent) + pThis->cb = pSrc->cb; + else + { + int rc = RTAsn1ContentDup(pThis, pSrc->uData.pv, pSrc->cb, pAllocator); + if (RT_FAILURE(rc)) + { + RT_ZERO(*pThis); + return rc; + } + Assert(pThis->cb == pSrc->cb); + AssertPtr(pThis->uData.pv); + } + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Core_CloneContent(PRTASN1CORE pThis, PCRTASN1CORE pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + return rtAsn1Core_CloneEx(pThis, pSrc, pAllocator, true /*fCopyContent*/); +} + + +RTDECL(int) RTAsn1Core_CloneNoContent(PRTASN1CORE pThis, PCRTASN1CORE pSrc) +{ + return rtAsn1Core_CloneEx(pThis, pSrc, NULL, false /*fCopyContent*/); +} + + +RTDECL(int) RTAsn1Core_CompareEx(PCRTASN1CORE pLeft, PCRTASN1CORE pRight, bool fIgnoreTagAndClass) +{ + int iDiff; + if (RTASN1CORE_IS_PRESENT(pLeft)) + { + if (RTASN1CORE_IS_PRESENT(pRight)) + { + iDiff = memcmp(pLeft->uData.pv, pRight->uData.pv, RT_MIN(pLeft->cb, pRight->cb)); + if (!iDiff) + { + if (pLeft->cb != pRight->cb) + iDiff = pLeft->cb < pRight->cb ? -1 : 1; + else if (!fIgnoreTagAndClass) + { + if (pLeft->uTag != pRight->uTag) + iDiff = pLeft->uTag < pRight->uTag ? -1 : 1; + else if (pLeft->fClass != pRight->fClass) + iDiff = pLeft->fClass < pRight->fClass ? -1 : 1; + } + } + else + iDiff = iDiff < 0 ? -1 : 1; + } + else + iDiff = -1; + } + else + iDiff = 0 - (int)RTASN1CORE_IS_PRESENT(pRight); + return iDiff; +} + + +/** + * @interface_method_impl{RTASN1COREVTABLE,pfnEncodePrep, + * This is for not dropping the unparsed content of a 'core' structure when + * re-encoding it. } + */ +static DECLCALLBACK(int) rtAsn1Core_EncodePrep(PRTASN1CORE pThisCore, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + /* We don't update anything here. */ + RT_NOREF(pThisCore, fFlags, pErrInfo); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTASN1COREVTABLE,pfnEncodeWrite, + * This is for not dropping the unparsed content of a 'core' structure when + * re-encoding it. } + */ +static DECLCALLBACK(int) rtAsn1Core_EncodeWrite(PRTASN1CORE pThisCore, uint32_t fFlags, PFNRTASN1ENCODEWRITER pfnWriter, + void *pvUser, PRTERRINFO pErrInfo) +{ + int rc = RTAsn1EncodeWriteHeader(pThisCore, fFlags, pfnWriter, pvUser, pErrInfo); + if ( RT_SUCCESS(rc) + && rc != VINF_ASN1_NOT_ENCODED) + { + Assert(!RTASN1CORE_IS_DUMMY(pThisCore)); + if (pThisCore->cb) + { + AssertPtrReturn(pThisCore->uData.pv, + RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_DATA_POINTER, + "Invalid uData pointer %p for lone ASN.1 core with %#x bytes of content", + pThisCore->uData.pv, pThisCore->cb)); + rc = pfnWriter(pThisCore->uData.pv, pThisCore->cb, pvUser, pErrInfo); + } + } + return rc; +} + + + +/* + * ASN.1 Core - Standard Methods. + * + * @note Children of the ASN.1 Core doesn't normally call these, they are for + * when RTASN1CORE is used as a member type. + */ + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Core_Vtable = +{ + "RTAsn1Core", + sizeof(RTASN1CORE), + UINT8_MAX, + UINT8_MAX, + 0, + RTAsn1Core_Delete, + RTAsn1Core_Enum, + (PFNRTASN1COREVTCLONE)RTAsn1Core_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1Core_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1Core_CheckSanity, + rtAsn1Core_EncodePrep, + rtAsn1Core_EncodeWrite +}; + + +RTDECL(int) RTAsn1Core_Init(PRTASN1CORE pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_NOREF_PV(pAllocator); + return RTAsn1Core_InitEx(pThis, 0, ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1Core_Vtable, RTASN1CORE_F_PRESENT); +} + + +RTDECL(int) RTAsn1Core_Clone(PRTASN1CORE pThis, PCRTASN1CORE pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + int rc; + RT_ZERO(*pThis); + if (RTASN1CORE_IS_PRESENT(pSrc)) + { + Assert(pSrc->pOps == &g_RTAsn1Core_Vtable); + + rc = RTAsn1Core_CloneContent(pThis, pSrc, pAllocator); + } + else + rc = VINF_SUCCESS; + return rc; +} + + +RTDECL(void) RTAsn1Core_Delete(PRTASN1CORE pThis) +{ + if (pThis && RTASN1CORE_IS_PRESENT(pThis)) + { + Assert(pThis->pOps == &g_RTAsn1Core_Vtable); + + RTAsn1ContentFree(pThis); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1Core_Enum(PRTASN1CORE pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + /* We have no children to enumerate. */ + Assert(pThis && (!RTASN1CORE_IS_PRESENT(pThis) || pThis->pOps == &g_RTAsn1Core_Vtable)); + NOREF(pThis); + NOREF(pfnCallback); + NOREF(uDepth); + NOREF(pvUser); + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Core_Compare(PCRTASN1CORE pLeft, PCRTASN1CORE pRight) +{ + Assert(pLeft && (!RTASN1CORE_IS_PRESENT(pLeft) || pLeft->pOps == &g_RTAsn1Core_Vtable)); + Assert(pRight && (!RTASN1CORE_IS_PRESENT(pRight) || pRight->pOps == &g_RTAsn1Core_Vtable)); + + return RTAsn1Core_CompareEx(pLeft, pRight, false /*fIgnoreTagAndClass*/); +} + + +RTDECL(int) RTAsn1Core_CheckSanity(PCRTASN1CORE pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + + /* We can only check that it's present. */ + if (!RTAsn1Core_IsPresent(pThis)) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (RTASN1CORE).", pszErrorTag); + return VINF_SUCCESS; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-core-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-core.h> +#include <iprt/asn1-generator-init.h> +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-dyntype-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-dyntype-decode.cpp new file mode 100644 index 00000000..bc48688a --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-dyntype-decode.cpp @@ -0,0 +1,250 @@ +/* $Id: asn1-ut-dyntype-decode.cpp $ */ +/** @file + * IPRT - ASN.1, Dynamic Type, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + + +RTDECL(int) RTAsn1DynType_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1DYNTYPE pDynType, const char *pszErrorTag) +{ + RT_ZERO(*pDynType); + + Assert(!(fFlags & RTASN1CURSOR_GET_F_IMPLICIT)); RT_NOREF_PV(fFlags); + uint32_t cbSavedLeft = pCursor->cbLeft; + uint8_t const *pbSavedCur = pCursor->pbCur; + + int rc = RTAsn1CursorReadHdr(pCursor, &pDynType->u.Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + pDynType->enmType = RTASN1TYPE_CORE; + + if (pDynType->u.Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE)) + { + switch (pDynType->u.Core.uTag) + { + case ASN1_TAG_BOOLEAN: + pDynType->enmType = RTASN1TYPE_BOOLEAN; + break; + case ASN1_TAG_INTEGER: + pDynType->enmType = RTASN1TYPE_INTEGER; + break; + //case ASN1_TAG_ENUMERATED: + // pDynType->enmType = RTASN1TYPE_ENUMERATED; + // break; + //case ASN1_TAG_REAL: + // pDynType->enmType = RTASN1TYPE_REAL; + // break; + case ASN1_TAG_BIT_STRING: + pDynType->enmType = RTASN1TYPE_BIT_STRING; + break; + case ASN1_TAG_OCTET_STRING: + pDynType->enmType = RTASN1TYPE_OCTET_STRING; + break; + case ASN1_TAG_NULL: + pDynType->enmType = RTASN1TYPE_NULL; + break; + case ASN1_TAG_SEQUENCE: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 SEQUENCE shall be constructed."); + case ASN1_TAG_SET: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 SET shall be constructed."); + case ASN1_TAG_OID: + pDynType->enmType = RTASN1TYPE_OBJID; + break; + //case ASN1_TAG_RELATIVE_OID: + // pDynType->enmType = RTASN1TYPE_RELATIVE_OBJID; + // break; + case ASN1_TAG_UTC_TIME: + case ASN1_TAG_GENERALIZED_TIME: + pDynType->enmType = RTASN1TYPE_TIME; + break; + case ASN1_TAG_UTF8_STRING: + case ASN1_TAG_NUMERIC_STRING: + case ASN1_TAG_PRINTABLE_STRING: + case ASN1_TAG_T61_STRING: + case ASN1_TAG_VIDEOTEX_STRING: + case ASN1_TAG_IA5_STRING: + case ASN1_TAG_GRAPHIC_STRING: + case ASN1_TAG_VISIBLE_STRING: + case ASN1_TAG_UNIVERSAL_STRING: + case ASN1_TAG_GENERAL_STRING: + case ASN1_TAG_BMP_STRING: + pDynType->enmType = RTASN1TYPE_STRING; + break; + //case ASN1_TAG_CHARACTER_STRING: + // pDynType->enmType = RTASN1TYPE_CHARACTER_STRING; + // break; + + default: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_TAG_NOT_IMPL, + "Primitive tag %u (%#x) not implemented.", + pDynType->u.Core.uTag, pDynType->u.Core.uTag); + } + } + else if (pDynType->u.Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED)) + switch (pDynType->u.Core.uTag) + { + case ASN1_TAG_BOOLEAN: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 BOOLEAN shall be primitive."); + case ASN1_TAG_INTEGER: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 BOOLEAN shall be primitive."); + case ASN1_TAG_ENUMERATED: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 ENUMERATED shall be primitive."); + case ASN1_TAG_REAL: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 REAL shall be primitive."); + case ASN1_TAG_BIT_STRING: + pDynType->enmType = RTASN1TYPE_BIT_STRING; + break; + case ASN1_TAG_OCTET_STRING: + pDynType->enmType = RTASN1TYPE_OCTET_STRING; + break; + case ASN1_TAG_NULL: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 NULL shall be primitive."); + case ASN1_TAG_SEQUENCE: +#if 0 + pDynType->enmType = RTASN1TYPE_SEQUENCE_CORE; + pDynType->u.SeqCore.Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + RTAsn1CursorSkip(pCursor, pDynType->u.Core.cb); + return VINF_SUCCESS; +#else + pDynType->enmType = RTASN1TYPE_CORE; +#endif + break; + case ASN1_TAG_SET: +#if 0 + pDynType->enmType = RTASN1TYPE_SET_CORE; + pDynType->u.SeqCore.Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + RTAsn1CursorSkip(pCursor, pDynType->u.Core.cb); + return VINF_SUCCESS; +#else + pDynType->enmType = RTASN1TYPE_CORE; +#endif + break; + case ASN1_TAG_OID: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 OBJECT ID shall be primitive."); + case ASN1_TAG_RELATIVE_OID: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 RELATIVE OID shall be primitive."); + + case ASN1_TAG_UTF8_STRING: + case ASN1_TAG_NUMERIC_STRING: + case ASN1_TAG_PRINTABLE_STRING: + case ASN1_TAG_T61_STRING: + case ASN1_TAG_VIDEOTEX_STRING: + case ASN1_TAG_IA5_STRING: + case ASN1_TAG_GRAPHIC_STRING: + case ASN1_TAG_VISIBLE_STRING: + case ASN1_TAG_UNIVERSAL_STRING: + case ASN1_TAG_GENERAL_STRING: + case ASN1_TAG_BMP_STRING: + pDynType->enmType = RTASN1TYPE_STRING; + break; + //case ASN1_TAG_CHARACTER_STRING: + // pDynType->enmType = RTASN1TYPE_CHARACTER_STRING; + // break; + + default: + RT_ZERO(*pDynType); + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_TAG_NOT_IMPL, + "Constructed tag %u (%#x) not implemented.", + pDynType->u.Core.uTag, pDynType->u.Core.uTag); + } + else + Assert(pDynType->enmType == RTASN1TYPE_CORE); + + /* + * Restore the cursor and redo with specific type. + */ + pCursor->pbCur = pbSavedCur; + pCursor->cbLeft = cbSavedLeft; + + switch (pDynType->enmType) + { + case RTASN1TYPE_INTEGER: + rc = RTAsn1Integer_DecodeAsn1(pCursor, 0, &pDynType->u.Integer, pszErrorTag); + break; + case RTASN1TYPE_BOOLEAN: + rc = RTAsn1Boolean_DecodeAsn1(pCursor, 0, &pDynType->u.Boolean, pszErrorTag); + break; + case RTASN1TYPE_OBJID: + rc = RTAsn1ObjId_DecodeAsn1(pCursor, 0, &pDynType->u.ObjId, pszErrorTag); + break; + case RTASN1TYPE_BIT_STRING: + rc = RTAsn1BitString_DecodeAsn1(pCursor, 0, &pDynType->u.BitString, pszErrorTag); + break; + case RTASN1TYPE_OCTET_STRING: + rc = RTAsn1OctetString_DecodeAsn1(pCursor, 0, &pDynType->u.OctetString, pszErrorTag); + break; + case RTASN1TYPE_NULL: + rc = RTAsn1Null_DecodeAsn1(pCursor, 0, &pDynType->u.Asn1Null, pszErrorTag); + break; + case RTASN1TYPE_TIME: + rc = RTAsn1Time_DecodeAsn1(pCursor, 0, &pDynType->u.Time, pszErrorTag); + break; + case RTASN1TYPE_STRING: + rc = RTAsn1String_DecodeAsn1(pCursor, 0, &pDynType->u.String, pszErrorTag); + break; + case RTASN1TYPE_CORE: + rc = RTAsn1Core_DecodeAsn1(pCursor, 0, &pDynType->u.Core, pszErrorTag); + break; + default: + AssertFailedReturn(VERR_INTERNAL_ERROR_4); + } + if (RT_SUCCESS(rc)) + return rc; + } + RT_ZERO(*pDynType); + return rc; +} + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp new file mode 100644 index 00000000..1708c71a --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp @@ -0,0 +1,197 @@ +/* $Id: asn1-ut-dyntype.cpp $ */ +/** @file + * IPRT - ASN.1, Basic Operations. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +/* + * ASN.1 dynamic type union - Standard Methods. + */ + + +RTDECL(int) RTAsn1DynType_Init(PRTASN1DYNTYPE pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_NOREF_PV(pAllocator); + RT_ZERO(*pThis); + pThis->enmType = RTASN1TYPE_NOT_PRESENT; + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1DynType_Clone(PRTASN1DYNTYPE pThis, PCRTASN1DYNTYPE pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); + RT_ZERO(*pThis); + if (RTAsn1DynType_IsPresent(pSrc)) + { + int rc; + switch (pSrc->enmType) + { + case RTASN1TYPE_CORE: rc = RTAsn1Core_Clone(&pThis->u.Core, &pSrc->u.Core, pAllocator); break; + case RTASN1TYPE_NULL: rc = RTAsn1Null_Clone(&pThis->u.Asn1Null, &pSrc->u.Asn1Null, pAllocator); break; + case RTASN1TYPE_INTEGER: rc = RTAsn1Integer_Clone(&pThis->u.Integer, &pSrc->u.Integer, pAllocator); break; + case RTASN1TYPE_BOOLEAN: rc = RTAsn1Boolean_Clone(&pThis->u.Boolean, &pSrc->u.Boolean, pAllocator); break; + case RTASN1TYPE_STRING: rc = RTAsn1String_Clone(&pThis->u.String, &pSrc->u.String, pAllocator); break; + case RTASN1TYPE_OCTET_STRING: rc = RTAsn1OctetString_Clone(&pThis->u.OctetString, &pSrc->u.OctetString, pAllocator); break; + case RTASN1TYPE_BIT_STRING: rc = RTAsn1BitString_Clone(&pThis->u.BitString, &pSrc->u.BitString, pAllocator); break; + case RTASN1TYPE_TIME: rc = RTAsn1Time_Clone(&pThis->u.Time, &pSrc->u.Time, pAllocator); break; +#if 0 + case RTASN1TYPE_SEQUENCE_CORE: rc = VERR_NOT_SUPPORTED; //rc = RTAsn1SequenceCore_Clone(&pThis->u.SeqCore, &pSrc->u.SeqCore, pAllocator); break; + case RTASN1TYPE_SET_CORE: rc = VERR_NOT_SUPPORTED; //rc = RTAsn1SetCore_Clone(&pThis->u.SetCore, &pSrc->u.SetCore, pAllocator); break; +#endif + case RTASN1TYPE_OBJID: rc = RTAsn1ObjId_Clone(&pThis->u.ObjId, &pSrc->u.ObjId, pAllocator); break; + default: + AssertFailedReturn(VERR_ASN1_INTERNAL_ERROR_2); + } + if (RT_FAILURE(rc)) + { + RT_ZERO(*pThis); + return rc; + } + pThis->enmType = pSrc->enmType; + } + else + pThis->enmType = RTASN1TYPE_NOT_PRESENT; + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1DynType_Delete(PRTASN1DYNTYPE pThis) +{ + if ( pThis + && RTAsn1DynType_IsPresent(pThis)) + { + if ( pThis->u.Core.pOps + && pThis->u.Core.pOps->pfnDtor) + pThis->u.Core.pOps->pfnDtor(&pThis->u.Core); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1DynType_SetToNull(PRTASN1DYNTYPE pThis) +{ + RTAsn1DynType_Delete(pThis); + pThis->enmType = RTASN1TYPE_NULL; + return RTAsn1Null_Init(&pThis->u.Asn1Null, NULL /*pAllocator*/); +} + + +RTDECL(int) RTAsn1DynType_SetToObjId(PRTASN1DYNTYPE pThis, PCRTASN1OBJID pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RTAsn1DynType_Delete(pThis); + pThis->enmType = RTASN1TYPE_OBJID; + return RTAsn1ObjId_Clone(&pThis->u.ObjId, pSrc, pAllocator); +} + + +RTDECL(int) RTAsn1DynType_Enum(PRTASN1DYNTYPE pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + if ( pThis + && RTAsn1DynType_IsPresent(pThis)) + { + if ( pThis->u.Core.pOps + && pThis->u.Core.pOps->pfnEnum) + return pThis->u.Core.pOps->pfnEnum(&pThis->u.Core, pfnCallback, uDepth, pvUser); + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1DynType_Compare(PCRTASN1DYNTYPE pLeft, PCRTASN1DYNTYPE pRight) +{ + if (RTAsn1DynType_IsPresent(pLeft) && RTAsn1DynType_IsPresent(pRight)) + { + if (pLeft->enmType != pRight->enmType) + return pLeft->enmType < pRight->enmType ? -1 : 1; + + switch (pLeft->enmType) + { + case RTASN1TYPE_CORE: return RTAsn1Core_Compare(&pLeft->u.Core, &pRight->u.Core); break; + case RTASN1TYPE_NULL: return RTAsn1Null_Compare(&pLeft->u.Asn1Null, &pRight->u.Asn1Null); + case RTASN1TYPE_INTEGER: return RTAsn1Integer_Compare(&pLeft->u.Integer, &pRight->u.Integer); + case RTASN1TYPE_BOOLEAN: return RTAsn1Boolean_Compare(&pLeft->u.Boolean, &pRight->u.Boolean); + case RTASN1TYPE_STRING: return RTAsn1String_Compare(&pLeft->u.String, &pRight->u.String); + case RTASN1TYPE_OCTET_STRING: return RTAsn1OctetString_Compare(&pLeft->u.OctetString, &pRight->u.OctetString); + case RTASN1TYPE_BIT_STRING: return RTAsn1BitString_Compare(&pLeft->u.BitString, &pRight->u.BitString); + case RTASN1TYPE_TIME: return RTAsn1Time_Compare(&pLeft->u.Time, &pRight->u.Time); + case RTASN1TYPE_OBJID: return RTAsn1ObjId_Compare(&pLeft->u.ObjId, &pRight->u.ObjId); + default: AssertFailedReturn(-1); + } + } + else + return (int)RTAsn1DynType_IsPresent(pLeft) - (int)RTAsn1DynType_IsPresent(pRight); +} + + +RTDECL(int) RTAsn1DynType_CheckSanity(PCRTASN1DYNTYPE pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + if (RT_UNLIKELY(!RTAsn1DynType_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (DYNTYPE).", pszErrorTag); + + int rc; + switch (pThis->enmType) + { + case RTASN1TYPE_CORE: rc = RTAsn1Core_CheckSanity(&pThis->u.Core, fFlags, pErrInfo, pszErrorTag); break; + case RTASN1TYPE_NULL: rc = RTAsn1Null_CheckSanity(&pThis->u.Asn1Null, fFlags, pErrInfo, pszErrorTag); break; + case RTASN1TYPE_INTEGER: rc = RTAsn1Integer_CheckSanity(&pThis->u.Integer, fFlags, pErrInfo, pszErrorTag); break; + case RTASN1TYPE_BOOLEAN: rc = RTAsn1Boolean_CheckSanity(&pThis->u.Boolean, fFlags, pErrInfo, pszErrorTag); break; + case RTASN1TYPE_STRING: rc = RTAsn1String_CheckSanity(&pThis->u.String, fFlags, pErrInfo, pszErrorTag); break; + case RTASN1TYPE_OCTET_STRING: rc = RTAsn1OctetString_CheckSanity(&pThis->u.OctetString, fFlags, pErrInfo, pszErrorTag); break; + case RTASN1TYPE_BIT_STRING: rc = RTAsn1BitString_CheckSanity(&pThis->u.BitString, fFlags, pErrInfo, pszErrorTag); break; + case RTASN1TYPE_TIME: rc = RTAsn1Time_CheckSanity(&pThis->u.Time, fFlags, pErrInfo, pszErrorTag); break; +#if 0 + case RTASN1TYPE_SEQUENCE_CORE: rc = VINF_SUCCESS; //rc = RTAsn1SequenceCore_CheckSanity(&pThis->u.SeqCore, fFlags, pErrInfo, pszErrorTag); break; + case RTASN1TYPE_SET_CORE: rc = VINF_SUCCESS; //rc = RTAsn1SetCore_CheckSanity(&pThis->u.SetCore, fFlags, pErrInfo, pszErrorTag); break; +#endif + case RTASN1TYPE_OBJID: rc = RTAsn1ObjId_CheckSanity(&pThis->u.ObjId, fFlags, pErrInfo, pszErrorTag); break; + default: + AssertFailedReturn(VERR_ASN1_INTERNAL_ERROR_2); + } + + return rc; +} + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-integer-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-integer-decode.cpp new file mode 100644 index 00000000..f753d453 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-integer-decode.cpp @@ -0,0 +1,97 @@ +/* $Id: asn1-ut-integer-decode.cpp $ */ +/** @file + * IPRT - ASN.1, INTEGER Type, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +RTDECL(int) RTAsn1Integer_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1INTEGER pThis, const char *pszErrorTag) +{ + pThis->uValue.u = 0; + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_INTEGER, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + fFlags, pszErrorTag, "INTEGER"); + if (RT_SUCCESS(rc)) + { + if (pThis->Asn1Core.cb > 0) + { + uint32_t offLast = pThis->Asn1Core.cb - 1; + switch (pThis->Asn1Core.cb) + { + default: + case 8: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 7] << 56; RT_FALL_THRU(); + case 7: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 6] << 48; RT_FALL_THRU(); + case 6: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 5] << 40; RT_FALL_THRU(); + case 5: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 4] << 32; RT_FALL_THRU(); + case 4: pThis->uValue.u |= (uint32_t)pThis->Asn1Core.uData.pu8[offLast - 3] << 24; RT_FALL_THRU(); + case 3: pThis->uValue.u |= (uint32_t)pThis->Asn1Core.uData.pu8[offLast - 2] << 16; RT_FALL_THRU(); + case 2: pThis->uValue.u |= (uint16_t)pThis->Asn1Core.uData.pu8[offLast - 1] << 8; RT_FALL_THRU(); + case 1: pThis->uValue.u |= pThis->Asn1Core.uData.pu8[offLast]; + } + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + pThis->Asn1Core.pOps = &g_RTAsn1Integer_Vtable; + return VINF_SUCCESS; + } + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_INTEGER_ENCODING, + "%s: Invalid integer length, exepcted more than 0: %#x", + pszErrorTag, pThis->Asn1Core.cb); + } + } + RT_ZERO(*pThis); + return rc; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-integer-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-integer-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-integer-template.h new file mode 100644 index 00000000..1dfbfa4e --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-integer-template.h @@ -0,0 +1,55 @@ +/* $Id: asn1-ut-integer-template.h $ */ +/** @file + * IPRT - ASN.1, INTEGER Type, Collection Type Template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +#define RTASN1TMPL_TYPE RTASN1SEQOFINTEGERS +#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfIntegers +#define RTASN1TMPL_INT_NAME rtAsn1SeqOfIntegers +RTASN1TMPL_SEQ_OF(RTASN1INTEGER, RTAsn1Integer); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFINTEGERS +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfIntegers +#define RTASN1TMPL_INT_NAME rtAsn1SetOfIntegers +RTASN1TMPL_SET_OF(RTASN1INTEGER, RTAsn1Integer); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp new file mode 100644 index 00000000..79df5c49 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp @@ -0,0 +1,508 @@ +/* $Id: asn1-ut-integer.cpp $ */ +/** @file + * IPRT - ASN.1, INTEGER Type. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/bignum.h> +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Fixed on-byte constants for small numbers. + * Good for structure version values and such. */ +static const uint8_t g_abSmall[] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +}; + + + +/* + * ASN.1 INTEGER - Special Methods. + */ + + +/** + * Updates the native value we keep in RTASN1INTEGER::uValue. + * + * @param pThis The integer. + */ +static void rtAsn1Integer_UpdateNativeValue(PRTASN1INTEGER pThis) +{ + uint32_t offLast = pThis->Asn1Core.cb - 1; + switch (pThis->Asn1Core.cb) + { + default: AssertBreak(pThis->Asn1Core.cb > 8); /* paranoia */ RT_FALL_THRU(); + case 8: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 7] << 56; RT_FALL_THRU(); + case 7: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 6] << 48; RT_FALL_THRU(); + case 6: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 5] << 40; RT_FALL_THRU(); + case 5: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 4] << 32; RT_FALL_THRU(); + case 4: pThis->uValue.u |= (uint32_t)pThis->Asn1Core.uData.pu8[offLast - 3] << 24; RT_FALL_THRU(); + case 3: pThis->uValue.u |= (uint32_t)pThis->Asn1Core.uData.pu8[offLast - 2] << 16; RT_FALL_THRU(); + case 2: pThis->uValue.u |= (uint16_t)pThis->Asn1Core.uData.pu8[offLast - 1] << 8; RT_FALL_THRU(); + case 1: pThis->uValue.u |= pThis->Asn1Core.uData.pu8[offLast]; + } +} + + +RTDECL(int) RTAsn1Integer_InitU64(PRTASN1INTEGER pThis, uint64_t uValue, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + /* + * Initialize the core and the native value. + */ + RTAsn1Core_InitEx(&pThis->Asn1Core, + ASN1_TAG_INTEGER, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1Integer_Vtable, + RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + pThis->uValue.u = uValue; + + /* + * Use one of the constants if possible. + */ + if (uValue < RT_ELEMENTS(g_abSmall)) + { + pThis->Asn1Core.cb = 1; + pThis->Asn1Core.uData.pv = (void *)&g_abSmall[0]; + } + else + { + /* + * Need to turn uValue into a big endian number without any + * unnecessary leading zero bytes. + */ + /* Figure the size. */ + uint32_t cb = 0; + if (uValue <= UINT32_MAX) + { + if (uValue <= UINT16_MAX) + { + if (uValue <= UINT8_MAX) + cb = 1; + else + cb = 2; + } + else + { + if (uValue <= UINT32_C(0xffffff)) + cb = 3; + else + cb = 4; + } + } + else + { + if (uValue <= UINT64_C(0x0000FfffFfffFfff)) + { + if (uValue <= UINT64_C(0x000000ffFfffFfff)) + cb = 5; + else + cb = 6; + } + else + { + if (uValue <= UINT64_C(0x00ffFfffFfffFfff)) + cb = 7; + else + cb = 8; + } + } + + /* Allocate space. */ + int rc = RTAsn1ContentAllocZ(&pThis->Asn1Core, cb, pAllocator); + if (RT_FAILURE(rc)) + { + RT_ZERO(*pThis); + return rc; + } + + /* Serialize the number in MSB order. */ + uint8_t *pb = (uint8_t *)pThis->Asn1Core.uData.pu8; + while (cb-- > 0) + { + pb[cb] = (uint8_t)uValue; + uValue >>= 8; + } + Assert(uValue == 0); + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Integer_InitDefault(PRTASN1INTEGER pThis, uint64_t uValue, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + int rc = RTAsn1Integer_InitU64(pThis, uValue, pAllocator); + if (RT_SUCCESS(rc)) + { + pThis->Asn1Core.fFlags &= ~RTASN1CORE_F_PRESENT; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_DEFAULT; + } + return rc; +} + + +RTDECL(int32_t) RTAsn1Integer_UnsignedLastBit(PCRTASN1INTEGER pThis) +{ + AssertReturn(pThis->Asn1Core.fFlags, -1); + uint8_t const *pb = pThis->Asn1Core.uData.pu8; + AssertReturn(pb, -1); + uint32_t cb = pThis->Asn1Core.cb; + AssertReturn(pThis->Asn1Core.cb < (uint32_t)INT32_MAX / 8, -1); + + while (cb-- > 0) + { + uint8_t b = *pb++; + if (b) + { + int32_t iRet = cb * 8; + if (b & 0x80) iRet += 7; + else if (b & 0x40) iRet += 6; + else if (b & 0x20) iRet += 5; + else if (b & 0x10) iRet += 4; + else if (b & 0x08) iRet += 3; + else if (b & 0x04) iRet += 2; + else if (b & 0x02) iRet += 1; + else Assert(b == 0x01); + return iRet; + } + } + return -1; +} + + +RTDECL(int) RTAsn1Integer_UnsignedCompare(PCRTASN1INTEGER pLeft, PCRTASN1INTEGER pRight) +{ + Assert(pLeft && (!RTAsn1Integer_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1Integer_Vtable)); + Assert(pRight && (!RTAsn1Integer_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1Integer_Vtable)); + + int iDiff; + if (RTAsn1Integer_IsPresent(pLeft)) + { + if (RTAsn1Integer_IsPresent(pRight)) + { + if ( pLeft->Asn1Core.cb > 8 + || pRight->Asn1Core.cb > 8) + { + uint32_t iLeft = RTAsn1Integer_UnsignedLastBit(pLeft); + uint32_t iRight = RTAsn1Integer_UnsignedLastBit(pRight); + if (iLeft != iRight) + return iLeft < iRight ? -1 : 1; + if ((int32_t)iLeft < 0) + return 0; /* Both are all zeros. */ + + uint32_t i = iLeft / 8; + if (i > 8) + { + uint8_t const *pbLeft = &pLeft->Asn1Core.uData.pu8[pLeft->Asn1Core.cb - i - 1]; + uint8_t const *pbRight = &pRight->Asn1Core.uData.pu8[pRight->Asn1Core.cb - i - 1]; + for (;;) + { + if (*pbLeft != *pbRight) + return *pbLeft < *pbRight ? -1 : 1; + if (--i <= 8) + break; + pbLeft++; + pbRight++; + } + } + } + + if (pLeft->uValue.u == pRight->uValue.u) + iDiff = 0; + else + iDiff = pLeft->uValue.u < pRight->uValue.u ? -1 : 1; + } + else + iDiff = -1; + } + else + iDiff = 0 - (int)RTAsn1Integer_IsPresent(pRight); + return iDiff; +} + + +RTDECL(int) RTAsn1Integer_UnsignedCompareWithU64(PCRTASN1INTEGER pThis, uint64_t u64Const) +{ + int iDiff; + if (RTAsn1Integer_IsPresent(pThis)) + { + if (pThis->Asn1Core.cb > 8) + { + int32_t iLast = RTAsn1Integer_UnsignedLastBit(pThis); + if (iLast >= 64) + return 1; + } + + if (pThis->uValue.u == u64Const) + iDiff = 0; + else + iDiff = pThis->uValue.u < u64Const ? -1 : 1; + } + else + iDiff = 1; + return iDiff; +} + + +RTDECL(int) RTAsn1Integer_UnsignedCompareWithU32(PCRTASN1INTEGER pThis, uint32_t u32Const) +{ + int iDiff; + if (RTAsn1Integer_IsPresent(pThis)) + { + if (pThis->Asn1Core.cb > 8) + { + int32_t iLast = RTAsn1Integer_UnsignedLastBit(pThis); + if (iLast >= 32) + return 1; + } + + if (pThis->uValue.u == u32Const) + iDiff = 0; + else + iDiff = pThis->uValue.u < u32Const ? -1 : 1; + } + else + iDiff = 1; + return iDiff; +} + + +RTDECL(int) RTAsn1Integer_ToBigNum(PCRTASN1INTEGER pThis, PRTBIGNUM pBigNum, uint32_t fBigNumInit) +{ + AssertReturn(!(fBigNumInit & ~( RTBIGNUMINIT_F_SENSITIVE | RTBIGNUMINIT_F_UNSIGNED | RTBIGNUMINIT_F_SIGNED + | RTBIGNUMINIT_F_ENDIAN_LITTLE | RTBIGNUMINIT_F_ENDIAN_BIG)), + VERR_INVALID_PARAMETER); + AssertReturn(RTAsn1Integer_IsPresent(pThis), VERR_INVALID_PARAMETER); + + if (!(fBigNumInit & (RTBIGNUMINIT_F_UNSIGNED | RTBIGNUMINIT_F_SIGNED))) + fBigNumInit |= RTBIGNUMINIT_F_SIGNED; + + if (!(fBigNumInit & (RTBIGNUMINIT_F_ENDIAN_BIG | RTBIGNUMINIT_F_ENDIAN_LITTLE))) + fBigNumInit |= RTBIGNUMINIT_F_ENDIAN_BIG; + + return RTBigNumInit(pBigNum, fBigNumInit, pThis->Asn1Core.uData.pv, pThis->Asn1Core.cb); +} + + +RTDECL(int) RTAsn1Integer_FromBigNum(PRTASN1INTEGER pThis, PCRTBIGNUM pBigNum, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pThis); AssertPtr(pBigNum); AssertPtr(pAllocator); + + /* Be nice and auto init the object. */ + if (!RTAsn1Integer_IsPresent(pThis)) + RTAsn1Integer_Init(pThis, NULL); + + uint32_t cb = RTBigNumByteWidth(pBigNum); Assert(cb > 0); + int rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, cb, pAllocator); + if (RT_SUCCESS(rc)) + { + Assert(cb == pThis->Asn1Core.cb); + rc = RTBigNumToBytesBigEndian(pBigNum, (void *)pThis->Asn1Core.uData.pv, cb); + if (RT_SUCCESS(rc)) + rtAsn1Integer_UpdateNativeValue(pThis); + } + return rc; +} + + +RTDECL(int) RTAsn1Integer_ToString(PCRTASN1INTEGER pThis, char *pszBuf, size_t cbBuf, uint32_t fFlags, size_t *pcbActual) +{ + AssertReturn(RTAsn1Integer_IsPresent(pThis), VERR_INVALID_PARAMETER); + AssertReturn(fFlags == 0, VERR_INVALID_FLAGS); + + /* + * We only do hex conversions via this API. + * Currently we consider all numbers to be unsigned. + */ + /** @todo Signed ASN.1 INTEGER. */ + int rc; + size_t cbActual; + if (pThis->Asn1Core.cb <= 8) + { + cbActual = 2 + pThis->Asn1Core.cb*2 + 1; + if (cbActual <= cbBuf) + { + ssize_t cchFormat = RTStrFormatU64(pszBuf, cbBuf, pThis->uValue.u, 16, (int)cbActual - 1 /*cchWidth*/, 0, + RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD); + rc = VINF_SUCCESS; + AssertStmt(cchFormat == (ssize_t)cbActual - 1, rc = VERR_INTERNAL_ERROR_3); + } + else + rc = VERR_BUFFER_OVERFLOW; + } + else + { + cbActual = pThis->Asn1Core.cb * 3 - 1 /* save one separator */ + 1 /* terminator */; + if (cbActual <= cbBuf) + { + rc = RTStrPrintHexBytes(pszBuf, cbBuf, pThis->Asn1Core.uData.pv, pThis->Asn1Core.cb, RTSTRPRINTHEXBYTES_F_SEP_SPACE); + Assert(rc == VINF_SUCCESS); + } + else + rc = VERR_BUFFER_OVERFLOW; + } + if (pcbActual) + *pcbActual = cbActual; + return rc; +} + + +/* + * ASN.1 INTEGER - Standard Methods. + */ + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Integer_Vtable = +{ + "RTAsn1Integer", + sizeof(RTASN1INTEGER), + ASN1_TAG_INTEGER, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + 0, + (PFNRTASN1COREVTDTOR)RTAsn1Integer_Delete, + NULL, + (PFNRTASN1COREVTCLONE)RTAsn1Integer_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1Integer_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1Integer_CheckSanity, + NULL, + NULL +}; + + +RTDECL(int) RTAsn1Integer_Init(PRTASN1INTEGER pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_NOREF_PV(pAllocator); + RTAsn1Core_InitEx(&pThis->Asn1Core, + ASN1_TAG_INTEGER, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1Integer_Vtable, + RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + pThis->uValue.u = 1; + pThis->Asn1Core.cb = 1; + pThis->Asn1Core.uData.pv = (void *)&g_abSmall[0]; + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Integer_Clone(PRTASN1INTEGER pThis, PCRTASN1INTEGER pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); + RT_ZERO(*pThis); + if (RTAsn1Integer_IsPresent(pSrc)) + { + AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1Integer_Vtable, VERR_INTERNAL_ERROR_3); + + int rc; + if ( pSrc->Asn1Core.cb != 1 + || pSrc->uValue.u >= RT_ELEMENTS(g_abSmall)) + { + /* Value is too large, copy it. */ + rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator); + if (RT_FAILURE(rc)) + return rc; + } + else + { + /* Use one of the const values. */ + rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); + if (RT_FAILURE(rc)) + return rc; + Assert(g_abSmall[pSrc->uValue.u] == pSrc->uValue.u); + pThis->Asn1Core.uData.pv = (void *)&g_abSmall[pSrc->uValue.u]; + } + pThis->uValue.u = pSrc->uValue.u; + } + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1Integer_Delete(PRTASN1INTEGER pThis) +{ + if ( pThis + && RTAsn1Integer_IsPresent(pThis)) + { + Assert(pThis->Asn1Core.pOps == &g_RTAsn1Integer_Vtable); + + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1Integer_Enum(PRTASN1INTEGER pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); + Assert(pThis && (!RTAsn1Integer_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1Integer_Vtable)); + + /* No children to enumerate. */ + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Integer_Compare(PCRTASN1INTEGER pLeft, PCRTASN1INTEGER pRight) +{ + return RTAsn1Integer_UnsignedCompare(pLeft, pRight); +} + + +RTDECL(int) RTAsn1Integer_CheckSanity(PCRTASN1INTEGER pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + if (RT_UNLIKELY(!RTAsn1Integer_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (INTEGER).", pszErrorTag); + return VINF_SUCCESS; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-integer-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-core.h> +#include <iprt/asn1-generator-init.h> +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-null-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-null-decode.cpp new file mode 100644 index 00000000..5c43df1b --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-null-decode.cpp @@ -0,0 +1,73 @@ +/* $Id: asn1-ut-null-decode.cpp $ */ +/** @file + * IPRT - ASN.1, NULL type, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +RTDECL(int) RTAsn1Null_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1NULL pThis, const char *pszErrorTag) +{ + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_NULL, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, fFlags, pszErrorTag, "NULL"); + if (RT_SUCCESS(rc)) + { + if (pThis->Asn1Core.cb == 0) + { + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + pThis->Asn1Core.pOps = &g_RTAsn1Null_Vtable; + return VINF_SUCCESS; + } + + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_NULL_ENCODING, + "%s: Expected NULL object to have zero length: %#x", pszErrorTag, pThis->Asn1Core.cb); + } + } + RT_ZERO(*pThis); + return rc; +} + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-null.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-null.cpp new file mode 100644 index 00000000..9828dc51 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-null.cpp @@ -0,0 +1,141 @@ +/* $Id: asn1-ut-null.cpp $ */ +/** @file + * IPRT - ASN.1, NULL type. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +/* + * ASN.1 NULL - Special Methods. + */ + + +/* + * ASN.1 NULL - Standard Methods. + */ + + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Null_Vtable = +{ + "RTAsn1Null", + sizeof(RTASN1NULL), + ASN1_TAG_NULL, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + 0, + (PFNRTASN1COREVTDTOR)RTAsn1Null_Delete, + (PFNRTASN1COREVTENUM)RTAsn1Null_Enum, + (PFNRTASN1COREVTCLONE)RTAsn1Null_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1Null_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1Null_CheckSanity, + NULL, + NULL +}; + + +RTDECL(int) RTAsn1Null_Init(PRTASN1NULL pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_NOREF_PV(pAllocator); + return RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_NULL, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1Null_Vtable, RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); +} + + +RTDECL(int) RTAsn1Null_Clone(PRTASN1NULL pThis, PCRTASN1NULL pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); RT_NOREF_PV(pAllocator); + RT_ZERO(*pThis); + if (RTAsn1Null_IsPresent(pSrc)) + { + AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1Null_Vtable, VERR_INTERNAL_ERROR_3); + AssertReturn(pSrc->Asn1Core.cb == 0, VERR_INTERNAL_ERROR_4); + + int rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1Null_Delete(PRTASN1NULL pThis) +{ + if ( pThis + && RTAsn1Null_IsPresent(pThis)) + { + Assert(pThis->Asn1Core.pOps == &g_RTAsn1Null_Vtable); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1Null_Enum(PRTASN1NULL pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); + Assert(pThis && (!RTAsn1Null_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1Null_Vtable)); + + /* No children to enumerate. */ + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Null_Compare(PCRTASN1NULL pLeft, PCRTASN1NULL pRight) +{ + Assert(pLeft && (!RTAsn1Null_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1Null_Vtable)); + Assert(pRight && (!RTAsn1Null_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1Null_Vtable)); + + return (int)RTAsn1Null_IsPresent(pLeft) - (int)RTAsn1Null_IsPresent(pRight); +} + + +RTDECL(int) RTAsn1Null_CheckSanity(PCRTASN1NULL pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + if (RT_UNLIKELY(!RTAsn1Null_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (NULL).", pszErrorTag); + return VINF_SUCCESS; +} + +/* No NULL object collections. */ + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-objid-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-objid-decode.cpp new file mode 100644 index 00000000..6d0e66a0 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-objid-decode.cpp @@ -0,0 +1,377 @@ +/* $Id: asn1-ut-objid-decode.cpp $ */ +/** @file + * IPRT - ASN.1, OBJECT IDENTIFIER Type, Decoder. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/alloca.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/ctype.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static char const g_achDigits[11] = "0123456789"; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +DECLHIDDEN(int) rtAsn1ObjId_InternalFormatComponent(uint32_t uValue, char **ppszObjId, size_t *pcbObjId); /* asn1-ut-objid.cpp */ + + +/** + * Internal worker for RTAsn1ObjId_DecodeAsn1 that formats a component, with a + * leading dot. + * + * @returns VBox status code (caller complains on failure). + * @param uValue The component ID value. + * @param ppszObjId Pointer to the output buffer pointer. + * @param pcbObjId Pointer to the remaining size of the output buffer. + */ +DECLHIDDEN(int) rtAsn1ObjId_InternalFormatComponent(uint32_t uValue, char **ppszObjId, size_t *pcbObjId) +{ + /* + * Format the number backwards. + */ + char szTmp[32]; + char *psz = &szTmp[sizeof(szTmp) - 1]; + *psz = '\0'; + + do + { + *--psz = g_achDigits[uValue % 10]; + uValue /= 10; + } while (uValue > 0); + + /* + * Do we have enough space? + * We add a dot and save space for the terminator. + */ + size_t cchNeeded = &szTmp[sizeof(szTmp) - 1] - psz; + if (1 + cchNeeded < *pcbObjId) + { + *pcbObjId -= cchNeeded + 1; + char *pszObjId = *ppszObjId; + *ppszObjId = pszObjId + cchNeeded + 1; + + *pszObjId = '.'; + memcpy(pszObjId + 1, psz, cchNeeded); + return VINF_SUCCESS; + } + + AssertFailed(); + return VERR_ASN1_OBJID_TOO_LONG_STRING_FORM; +} + + +/** + * Reads one object ID component, returning it's value and encoded length. + * + * @returns The encoded length (positive) on success, negative IPRT status code + * on failure. + * @param pbContent The start of the component to parse. + * @param cbContent The number of content bytes left. + * @param puValue Where to return the value. + */ +static int rtAsn1ObjId_ReadComponent(uint8_t const *pbContent, uint32_t cbContent, uint32_t *puValue) +{ + if (cbContent >= 1) + { + /* The first byte. */ + uint8_t b = *pbContent; + if (!(b & 0x80)) + { + *puValue = b; + return 1; + } + + /* Encoded as more than one byte. Make sure that it's efficently + encoded as 8.19.2 indicates it must. */ + if (b != 0x80) + { + uint32_t off = 1; + uint32_t uValue = b & 0x7f; + while (off < cbContent) + { + b = pbContent[off++]; + uValue <<= 7; + uValue |= b & 0x7f; + if (!(b & 0x80)) + { + *puValue = uValue; + return (int)off; + } + + if (RT_UNLIKELY(uValue & UINT32_C(0x0e000000))) + return VERR_ASN1_OBJID_COMPONENT_TOO_BIG; + } + } + return VERR_ASN1_INVALID_OBJID_ENCODING; + } + return VERR_NO_DATA; +} + + +/** + * This function parses the binary content of an OBJECT IDENTIFIER, check the + * encoding as well as calculating the storage requirements. + * + * @returns IPRT status code + * @param pbContent Pointer to the content. + * @param cbContent The length of the content. + * @param pCursor The cursor (for error reporting). + * @param pszErrorTag The error tag. + * @param pcComponents Where to return the component count. + * @param pcchObjId Where to return the length of the dotted string + * representation. + */ +static int rtAsn1ObjId_PreParse(uint8_t const *pbContent, uint32_t cbContent, PRTASN1CURSOR pCursor, const char *pszErrorTag, + uint8_t *pcComponents, uint8_t *pcchObjId) +{ + int rc; + if (cbContent >= 1 && cbContent < _1K) + { + /* + * Decode the first two numbers. Monkey business: X*40 + Y + * Where X is the first number, X in {0,1,2}, and Y is the second + * one. The range of Y is {0,...,39} for X in {0,1}, but has a + * free range for X = 2. + */ + uint32_t cComponents = 1; + uint32_t uValue; + rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue); + if (rc > 0) + { + uint32_t cchObjId = 1; + uValue = uValue < 2*40 ? uValue % 40 : uValue - 2*40; /* Y */ + do + { + cComponents++; + + /* Figure the encoded string length, binary search fashion. */ + if (uValue < 10000) + { + if (uValue < 100) + { + if (uValue < 10) + cchObjId += 1 + 1; + else + cchObjId += 1 + 2; + } + else + { + if (uValue < 1000) + cchObjId += 1 + 3; + else + cchObjId += 1 + 4; + } + } + else + { + if (uValue < 1000000) + { + if (uValue < 100000) + cchObjId += 1 + 5; + else + cchObjId += 1 + 6; + } + else + { + if (uValue < 10000000) + cchObjId += 1 + 7; + else if (uValue < 100000000) + cchObjId += 1 + 8; + else + cchObjId += 1 + 9; + } + } + + /* advance. */ + pbContent += rc; + cbContent -= rc; + if (!cbContent) + { + if (cComponents < 128) + { + if (cchObjId < RT_SIZEOFMEMB(RTASN1OBJID, szObjId)) + { + *pcComponents = cComponents; + *pcchObjId = cchObjId; + return VINF_SUCCESS; + } + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_OBJID_TOO_LONG_STRING_FORM, + "%s: Object ID has a too long string form: %#x (max %#x)", + pszErrorTag, cchObjId, RT_SIZEOFMEMB(RTASN1OBJID, szObjId)); + } + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_OBJID_TOO_MANY_COMPONENTS, + "%s: Object ID has too many components: %#x (max 127)", pszErrorTag, cComponents); + } + + /* next */ + rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue); + } while (rc > 0); + } + rc = RTAsn1CursorSetInfo(pCursor, rc, "%s: Bad object ID component #%u encoding: %.*Rhxs", + pszErrorTag, cComponents, cbContent, pbContent); + } + else if (cbContent) + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_OBJID_ENCODING, "%s: Object ID content is loo long: %#x", + pszErrorTag, cbContent); + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_OBJID_ENCODING, "%s: Zero length object ID content", pszErrorTag); + return rc; +} + + + +RTDECL(int) RTAsn1ObjId_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OBJID pThis, const char *pszErrorTag) +{ + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_OID, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, fFlags, pszErrorTag, "OID"); + if (RT_SUCCESS(rc)) + { + /* + * Validate and count things first. + */ + uint8_t cComponents = 0; /* gcc maybe-crap */ + uint8_t cchObjId = 0; /* ditto */ + rc = rtAsn1ObjId_PreParse(pCursor->pbCur, pThis->Asn1Core.cb, pCursor, pszErrorTag, &cComponents, &cchObjId); + if (RT_SUCCESS(rc)) + { + /* + * Allocate memory for the components array, either out of the + * string buffer or off the heap. + */ + pThis->cComponents = cComponents; + RTAsn1CursorInitAllocation(pCursor, &pThis->Allocation); +#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */ + if (cComponents * sizeof(uint32_t) <= sizeof(pThis->szObjId) - cchObjId - 1) + pThis->pauComponents = (uint32_t *)&pThis->szObjId[sizeof(pThis->szObjId) - cComponents * sizeof(uint32_t)]; + else +#endif + rc = RTAsn1MemAllocZ(&pThis->Allocation, (void **)&pThis->pauComponents, + cComponents * sizeof(pThis->pauComponents[0])); + if (RT_SUCCESS(rc)) + { + uint32_t *pauComponents = (uint32_t *)pThis->pauComponents; + + /* + * Deal with the two first components first since they are + * encoded in a weird way to save a byte. + */ + uint8_t const *pbContent = pCursor->pbCur; + uint32_t cbContent = pThis->Asn1Core.cb; + uint32_t uValue; + rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + pbContent += rc; + cbContent -= rc; + + if (uValue < 80) + { + pauComponents[0] = uValue / 40; + pauComponents[1] = uValue % 40; + } + else + { + pauComponents[0] = 2; + pauComponents[1] = uValue - 2*40; + } + + char *pszObjId = &pThis->szObjId[0]; + *pszObjId++ = g_achDigits[pauComponents[0]]; + size_t cbObjIdLeft = cchObjId + 1 - 1; + + rc = rtAsn1ObjId_InternalFormatComponent(pauComponents[1], &pszObjId, &cbObjIdLeft); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /* + * The other components are encoded in less complicated manner. + */ + for (uint32_t i = 2; i < cComponents; i++) + { + rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue); + AssertRCBreak(rc); + pbContent += rc; + cbContent -= rc; + pauComponents[i] = uValue; + rc = rtAsn1ObjId_InternalFormatComponent(uValue, &pszObjId, &cbObjIdLeft); + AssertRCBreak(rc); + } + if (RT_SUCCESS(rc)) + { + Assert(cbObjIdLeft == 1); + *pszObjId = '\0'; + + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + pThis->Asn1Core.pOps = &g_RTAsn1ObjId_Vtable; + return VINF_SUCCESS; + } + } + } + RTAsn1MemFree(&pThis->Allocation, (void *)pThis->pauComponents); + pThis->pauComponents = NULL; + } + } + } + } + RT_ZERO(*pThis); + return rc; +} + + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-objid-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-objid-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-objid-template.h new file mode 100644 index 00000000..102aae15 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-objid-template.h @@ -0,0 +1,64 @@ +/* $Id: asn1-ut-objid-template.h $ */ +/** @file + * IPRT - ASN.1, OBJECT IDENTIFIER Type, Collection Type Template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +#define RTASN1TMPL_TYPE RTASN1SEQOFOBJIDS +#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfObjIds +#define RTASN1TMPL_INT_NAME rtAsn1SeqOfObjIds +RTASN1TMPL_SEQ_OF(RTASN1OBJID, RTAsn1ObjId); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFOBJIDS +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfObjIds +#define RTASN1TMPL_INT_NAME rtAsn1SetOfObjIds +RTASN1TMPL_SET_OF(RTASN1OBJID, RTAsn1ObjId); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFOBJIDSEQS +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfObjIdSeqs +#define RTASN1TMPL_INT_NAME rtAsn1SetOfObjIdSeqs +RTASN1TMPL_SET_OF(RTASN1SEQOFOBJIDS, RTAsn1SeqOfObjIds); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp new file mode 100644 index 00000000..d1212437 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp @@ -0,0 +1,566 @@ +/* $Id: asn1-ut-objid.cpp $ */ +/** @file + * IPRT - ASN.1, OBJECT IDENTIFIER Type. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/alloca.h> +#include <iprt/bignum.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static char const g_szDefault[] = "2.16.840.1.113894"; +static uint32_t const g_auDefault[] = { 2, 16, 840, 1, 113894 }; +static uint8_t const g_abDefault[] = +{ + 2*40 + 16, 0x80 | (840 >> 7), 840 & 0x7f, 1, 0x80 | (113894 >> 14), 0x80 | ((113894 >> 7) & 0x7f), 113894 & 0x7f +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +DECLHIDDEN(int) rtAsn1ObjId_InternalFormatComponent(uint32_t uValue, char **ppszObjId, size_t *pcbObjId); /* asn1-ut-objid.cpp */ +/** @todo check if we really need this. */ + + + +/* + * ASN.1 OBJECT IDENTIFIER - Special Methods. + */ + +/** + * Encodes the ASN.1 byte sequence for a set of components. + * + * @returns IPRT status code. + * @param cComponents The number of components. Must be at least two. + * @param pauComponents The components array. + * @param pbEncoded The output buffer. + * @param pcbEncoded On input, this holds the size of the output buffer. + * On successful return it's the encoded size in bytes. + */ +static int rtAsn1ObjId_EncodeComponents(uint32_t cComponents, uint32_t const *pauComponents, + uint8_t *pbEncoded, uint32_t *pcbEncoded) +{ + uint8_t *pbCur = pbEncoded; + uint32_t cbLeft = *pcbEncoded; + + /* The first two componets are encoded together to save a byte, so the loop + organization is a little special. */ + AssertReturn(cComponents >= 2, VERR_ASN1_INTERNAL_ERROR_1); + AssertReturn(pauComponents[0] <= 2, VERR_ASN1_INTERNAL_ERROR_1); + AssertReturn(pauComponents[1] <= (pauComponents[0] < 2 ? 39 : UINT32_MAX - 80), VERR_ASN1_INTERNAL_ERROR_1); + uint32_t i = 1; + uint32_t uValue = pauComponents[0] * 40 + pauComponents[1]; + + for (;;) + { + if (uValue < 0x80) + { + if (RT_UNLIKELY(cbLeft < 1)) + return VERR_BUFFER_OVERFLOW; + cbLeft -= 1; + *pbCur++ = (uint8_t)uValue; + } + else if (uValue < 0x4000) + { + if (RT_UNLIKELY(cbLeft < 2)) + return VERR_BUFFER_OVERFLOW; + cbLeft -= 2; + pbCur[0] = (uValue >> 7) | 0x80; + pbCur[1] = uValue & 0x7f; + pbCur += 2; + } + else if (uValue < 0x200000) + { + if (RT_UNLIKELY(cbLeft < 3)) + return VERR_BUFFER_OVERFLOW; + cbLeft -= 3; + pbCur[0] = (uValue >> 14) | 0x80; + pbCur[1] = ((uValue >> 7) & 0x7f) | 0x80; + pbCur[2] = uValue & 0x7f; + pbCur += 3; + } + else if (uValue < 0x10000000) + { + if (RT_UNLIKELY(cbLeft < 4)) + return VERR_BUFFER_OVERFLOW; + cbLeft -= 4; + pbCur[0] = (uValue >> 21) | 0x80; + pbCur[1] = ((uValue >> 14) & 0x7f) | 0x80; + pbCur[2] = ((uValue >> 7) & 0x7f) | 0x80; + pbCur[3] = uValue & 0x7f; + pbCur += 4; + } + else + { + if (RT_UNLIKELY(cbLeft < 5)) + return VERR_BUFFER_OVERFLOW; + cbLeft -= 5; + pbCur[0] = (uValue >> 28) | 0x80; + pbCur[1] = ((uValue >> 21) & 0x7f) | 0x80; + pbCur[2] = ((uValue >> 14) & 0x7f) | 0x80; + pbCur[3] = ((uValue >> 7) & 0x7f) | 0x80; + pbCur[4] = uValue & 0x7f; + pbCur += 5; + } + + /* Advance / return. */ + i++; + if (i >= cComponents) + { + *pcbEncoded = (uint32_t)(pbCur - pbEncoded); + return VINF_SUCCESS; + } + uValue = pauComponents[i]; + } +} + + +RTDECL(int) RTAsn1ObjId_InitFromString(PRTASN1OBJID pThis, const char *pszObjId, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_ZERO(*pThis); + + /* + * Check the string, counting the number of components and checking their validity. + */ + size_t cbObjId = strlen(pszObjId) + 1; + AssertReturn(cbObjId < sizeof(pThis->szObjId), VERR_ASN1_OBJID_TOO_LONG_STRING_FORM); + + const char *psz = pszObjId; + + /* Special checking of the first component. It has only three valid values: 0,1,2. */ + char ch = *psz++; + if (RT_UNLIKELY(ch < '0' || ch > '2')) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + char const chFirst = ch; + ch = *psz++; + if (RT_UNLIKELY(ch != '.')) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + + /* The 2nd component. It the first is 0 or 1, it has a max of 39. */ + uint32_t cComponents = 1; + if (chFirst < '2') + { + ch = *psz++; + if (*psz == '.') + { + if (RT_UNLIKELY(!RT_C_IS_DIGIT(ch))) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + } + else + { + if (RT_UNLIKELY(ch < '0' || ch > '3')) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + ch = *psz++; + if (RT_UNLIKELY(!RT_C_IS_DIGIT(ch))) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + if (*psz != '.') + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + } + cComponents++; + } + else + psz--; + + /* Subsequent components have max values of UINT32_MAX - 80. */ + while ((ch = *psz++) != '\0') + { + if (RT_UNLIKELY(ch != '.')) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + const char *pszStart = psz; + + /* Special treatment of the first digit. Need to make sure it isn't an + unnecessary leading 0. */ + ch = *psz++; + if (RT_UNLIKELY(!RT_C_IS_DIGIT(ch))) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + if (RT_UNLIKELY(ch == '0' && RT_C_IS_DIGIT(*psz))) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + + /* The rest of the digits. */ + while ((ch = *psz) != '.' && ch != '\0') + { + if (RT_UNLIKELY(!RT_C_IS_DIGIT(ch))) + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + psz++; + } + + /* Check the value range. */ + if (RT_UNLIKELY(psz - pszStart >= 9)) + if ( psz - pszStart > 9 + || strncmp(pszStart, "4294967216", 9) >= 0) /* 2^32 - 80 */ + return VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + + cComponents++; + } + + if (RT_UNLIKELY(cComponents >= 128)) + return VERR_ASN1_OBJID_TOO_MANY_COMPONENTS; + pThis->cComponents = (uint8_t)cComponents; + + /* + * Find space for the component array, either at the unused end of szObjId + * or on the heap. + */ + int rc; + RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator); +#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */ + size_t cbLeft = sizeof(pThis->szObjId) - cbObjId; + if (cbLeft >= cComponents * sizeof(uint32_t)) + { + pThis->pauComponents = (uint32_t *)&pThis->szObjId[sizeof(pThis->szObjId) - cComponents * sizeof(uint32_t)]; + cbLeft -= cComponents * sizeof(uint32_t); + rc = VINF_SUCCESS; + } + else +#endif + rc = RTAsn1MemAllocZ(&pThis->Allocation, (void **)&pThis->pauComponents, cComponents * sizeof(uint32_t)); + if (RT_SUCCESS(rc)) + { + /* + * Fill the elements array. + */ + uint32_t *pauComponents = (uint32_t *)pThis->pauComponents; + rc = VINF_SUCCESS; + psz = pszObjId; + for (uint32_t i = 0; i < cComponents; i++) + { + uint32_t uValue = 0; + rc = RTStrToUInt32Ex(psz, (char **)&psz, 10, &uValue); + if (rc == VWRN_TRAILING_CHARS) + { + pauComponents[i] = uValue; + AssertBreakStmt(*psz == '.', rc = VERR_TRAILING_CHARS); + psz++; + } + else if (rc == VINF_SUCCESS) + { + pauComponents[i] = uValue; + Assert(*psz == '\0'); + } + else if (RT_FAILURE(rc)) + break; + else + { + rc = -rc; + break; + } + } + if (rc == VINF_SUCCESS && *psz == '\0') + { + /* + * Initialize the core structure before we start on the encoded bytes. + */ + RTAsn1Core_InitEx(&pThis->Asn1Core, + ASN1_TAG_OID, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1ObjId_Vtable, + RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + + /* + * Encode the value into the string buffer. This will NOT overflow + * because the string representation is much less efficient than the + * binary ASN.1 representation (base-10 + separators vs. base-128). + */ + pThis->Asn1Core.cb = (uint32_t)cbObjId; + rc = rtAsn1ObjId_EncodeComponents(cComponents, pThis->pauComponents, + (uint8_t *)&pThis->szObjId[0], &pThis->Asn1Core.cb); + if (RT_SUCCESS(rc)) + { + /* + * Now, find a place for the encoded bytes. There might be + * enough room left in the szObjId for it if we're lucky. + */ +#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */ + if (pThis->Asn1Core.cb >= cbLeft) + pThis->Asn1Core.uData.pv = memmove(&pThis->szObjId[cbObjId], &pThis->szObjId[0], pThis->Asn1Core.cb); + else +#endif + rc = RTAsn1ContentDup(&pThis->Asn1Core, pThis->szObjId, pThis->Asn1Core.cb, pAllocator); + if (RT_SUCCESS(rc)) + { + /* + * Finally, copy the dotted string. + */ + memcpy(pThis->szObjId, pszObjId, cbObjId); + return VINF_SUCCESS; + } + } + else + { + AssertMsgFailed(("%Rrc\n", rc)); + rc = VERR_ASN1_INTERNAL_ERROR_3; + } + } + else + rc = VERR_ASN1_OBJID_INVALID_DOTTED_STRING; + } + RT_ZERO(*pThis); + return rc; +} + + +RTDECL(int) RTAsn1ObjId_SetFromString(PRTASN1OBJID pThis, const char *pszObjId, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RTAsn1ObjId_Delete(pThis); + int rc = RTAsn1ObjId_InitFromString(pThis, pszObjId, pAllocator); + if (RT_FAILURE(rc)) + RTAsn1ObjId_Init(pThis, pAllocator); + return rc; +} + + +RTDECL(int) RTAsn1ObjId_CompareWithString(PCRTASN1OBJID pThis, const char *pszRight) +{ + return strcmp(pThis->szObjId, pszRight); +} + + +RTDECL(bool) RTAsn1ObjId_StartsWith(PCRTASN1OBJID pThis, const char *pszStartsWith) +{ + size_t cchStartsWith = strlen(pszStartsWith); + return !strncmp(pThis->szObjId, pszStartsWith, cchStartsWith) + && ( pszStartsWith[cchStartsWith] == '.' + || pszStartsWith[cchStartsWith] == '\0'); +} + + +RTDECL(uint8_t) RTAsn1ObjIdCountComponents(PCRTASN1OBJID pThis) +{ + return pThis->cComponents; +} + + +RTDECL(uint32_t) RTAsn1ObjIdGetComponentsAsUInt32(PCRTASN1OBJID pThis, uint8_t iComponent) +{ + if (iComponent < pThis->cComponents) + return pThis->pauComponents[iComponent]; + return UINT32_MAX; +} + + +RTDECL(uint32_t) RTAsn1ObjIdGetLastComponentsAsUInt32(PCRTASN1OBJID pThis) +{ + return pThis->pauComponents[pThis->cComponents - 1]; +} + + +/* + * ASN.1 OBJECT IDENTIFIER - Standard Methods. + */ + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1ObjId_Vtable = +{ + "RTAsn1ObjId", + sizeof(RTASN1OBJID), + ASN1_TAG_OID, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + 0, + (PFNRTASN1COREVTDTOR)RTAsn1ObjId_Delete, + NULL, + (PFNRTASN1COREVTCLONE)RTAsn1ObjId_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1ObjId_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1ObjId_CheckSanity, + NULL, + NULL +}; + + +RTDECL(int) RTAsn1ObjId_Init(PRTASN1OBJID pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_NOREF_PV(pAllocator); + RTAsn1Core_InitEx(&pThis->Asn1Core, + ASN1_TAG_OID, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1ObjId_Vtable, + RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + pThis->Asn1Core.cb = sizeof(g_abDefault); + pThis->Asn1Core.uData.pv = (void *)&g_abDefault[0]; + pThis->cComponents = RT_ELEMENTS(g_auDefault); + pThis->pauComponents = g_auDefault; + AssertCompile(sizeof(g_szDefault) <= sizeof(pThis->szObjId)); + memcpy(pThis->szObjId, g_szDefault, sizeof(g_szDefault)); + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1ObjId_Clone(PRTASN1OBJID pThis, PCRTASN1OBJID pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); + RT_ZERO(*pThis); + if (RTAsn1ObjId_IsPresent(pSrc)) + { + AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1ObjId_Vtable, VERR_INTERNAL_ERROR_3); + + /* Copy the dotted string representation. */ + size_t cbObjId = strlen(pSrc->szObjId) + 1; + AssertReturn(cbObjId <= sizeof(pThis->szObjId), VERR_INTERNAL_ERROR_5); + memcpy(pThis->szObjId, pSrc->szObjId, cbObjId); + + /* Copy the integer component array. Try fit it in the unused space of + the dotted object string buffer. We place it at the end of the + buffer as that is simple alignment wise and avoid wasting bytes that + could be used to sequueze in the content bytes (see below). */ + int rc; + RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator); + pThis->cComponents = pSrc->cComponents; +#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */ + size_t cbLeft = sizeof(pThis->szObjId); + if (pSrc->cComponents * sizeof(uint32_t) <= cbLeft) + { + pThis->pauComponents = (uint32_t *)&pThis->szObjId[sizeof(pThis->szObjId) - pSrc->cComponents * sizeof(uint32_t)]; + memcpy((uint32_t *)pThis->pauComponents, pSrc->pauComponents, pSrc->cComponents * sizeof(uint32_t)); + cbLeft -= pSrc->cComponents * sizeof(uint32_t); + rc = VINF_SUCCESS; + } + else +#endif + { + rc = RTAsn1MemDup(&pThis->Allocation, (void **)&pThis->pauComponents, pSrc->pauComponents, + pSrc->cComponents * sizeof(uint32_t)); + } + if (RT_SUCCESS(rc)) + { + /* See if we can fit the content value into the szObjId as well. + It will follow immediately after the string as the component + array is the end of the string buffer, when present. */ +#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */ + uint32_t cbContent = pSrc->Asn1Core.cb; + if (cbContent <= cbLeft) + { + rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); + if (RT_SUCCESS(rc)) + { + pThis->Asn1Core.uData.pv = memcpy(&pThis->szObjId[cbObjId], pSrc->Asn1Core.uData.pv, cbContent); + return VINF_SUCCESS; + } + } + else +#endif + { + rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + } + } + + /* failed, clean up. */ + if (pThis->Allocation.cbAllocated) + RTAsn1MemFree(&pThis->Allocation, (uint32_t *)pThis->pauComponents); + RT_ZERO(*pThis); + return rc; + } + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1ObjId_Delete(PRTASN1OBJID pThis) +{ + if ( pThis + && RTAsn1ObjId_IsPresent(pThis)) + { + Assert(pThis->Asn1Core.pOps == &g_RTAsn1ObjId_Vtable); + + if (pThis->Allocation.cbAllocated) + RTAsn1MemFree(&pThis->Allocation, (uint32_t *)pThis->pauComponents); + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1ObjId_Enum(PRTASN1OBJID pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); + Assert(pThis && (!RTAsn1ObjId_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1ObjId_Vtable)); + + /* No children to enumerate. */ + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1ObjId_Compare(PCRTASN1OBJID pLeft, PCRTASN1OBJID pRight) +{ + if (RTAsn1ObjId_IsPresent(pLeft)) + { + if (RTAsn1ObjId_IsPresent(pRight)) + { + uint8_t cComponents = RT_MIN(pLeft->cComponents, pRight->cComponents); + for (uint32_t i = 0; i < cComponents; i++) + if (pLeft->pauComponents[i] != pRight->pauComponents[i]) + return pLeft->pauComponents[i] < pRight->pauComponents[i] ? -1 : 1; + + if (pLeft->cComponents == pRight->cComponents) + return 0; + return pLeft->cComponents < pRight->cComponents ? -1 : 1; + } + return 1; + } + return 0 - (int)RTAsn1ObjId_IsPresent(pRight); +} + + +RTDECL(int) RTAsn1ObjId_CheckSanity(PCRTASN1OBJID pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + if (RT_UNLIKELY(!RTAsn1ObjId_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (OBJID).", pszErrorTag); + return VINF_SUCCESS; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-objid-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-core.h> +#include <iprt/asn1-generator-init.h> +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-decode.cpp new file mode 100644 index 00000000..fbe8aaf5 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-decode.cpp @@ -0,0 +1,90 @@ +/* $Id: asn1-ut-octetstring-decode.cpp $ */ +/** @file + * IPRT - ASN.1, OCTET STRING Type, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/formats/asn1.h> + + +RTDECL(int) RTAsn1OctetString_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OCTETSTRING pThis, + const char *pszErrorTag) +{ + pThis->pEncapsulated = NULL; + RTAsn1CursorInitAllocation(pCursor, &pThis->EncapsulatedAllocation); + + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlagsString(pCursor, &pThis->Asn1Core, ASN1_TAG_OCTET_STRING, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + fFlags, pszErrorTag, "OCTET STRING"); + if (RT_SUCCESS(rc)) + { + if ( !(pThis->Asn1Core.fClass & ASN1_TAGFLAG_CONSTRUCTED) + || (fFlags & RTASN1CURSOR_GET_F_IMPLICIT) ) /* PKCS #7 ContentInfo tweak. */ + { + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1OctetString_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + return VINF_SUCCESS; + } + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CONSTRUCTED_STRING_NOT_IMPL, + "%s: Constructed OCTET STRING not implemented.", pszErrorTag); + } + else + rc = RTAsn1CursorSetInfo(pCursor, rc, "%s: Not OCTET STRING: fClass=%#x / uTag=%#x", + pszErrorTag, pThis->Asn1Core.fClass, pThis->Asn1Core.uTag); + } + RT_ZERO(*pThis); + return rc; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-octetstring-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-template.h new file mode 100644 index 00000000..b3668922 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-template.h @@ -0,0 +1,55 @@ +/* $Id: asn1-ut-octetstring-template.h $ */ +/** @file + * IPRT - ASN.1, Octet String Type, Collection Type Template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +#define RTASN1TMPL_TYPE RTASN1SEQOFOCTETSTRINGS +#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfOctetStrings +#define RTASN1TMPL_INT_NAME rtAsn1SeqOfOctetStrings +RTASN1TMPL_SEQ_OF(RTASN1OCTETSTRING, RTAsn1OctetString); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFOCTETSTRINGS +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfOctetStrings +#define RTASN1TMPL_INT_NAME rtAsn1SetOfOctetStrings +RTASN1TMPL_SET_OF(RTASN1OCTETSTRING, RTAsn1OctetString); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-octetstring.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring.cpp new file mode 100644 index 00000000..32d6f40e --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring.cpp @@ -0,0 +1,459 @@ +/* $Id: asn1-ut-octetstring.cpp $ */ +/** @file + * IPRT - ASN.1, Octet String. + * + * @remarks This file should remain very similar to asn1-ut-bitstring.cpp. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/alloca.h> +#include <iprt/bignum.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RTASN1OCTETSTRINGWRITERCTX +{ + /** Pointer to the output buffer. */ + uint8_t *pbBuf; + /** The current buffer offset. */ + uint32_t offBuf; + /** The size of the buffer. */ + uint32_t cbBuf; +} RTASN1OCTETSTRINGWRITERCTX; + + +/** @callback_method_impl{FNRTASN1ENCODEWRITER, + * Used to refresh the content of octet and bit strings. } */ +static DECLCALLBACK(int) rtAsn1OctetStringEncodeWriter(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo) +{ + RTASN1OCTETSTRINGWRITERCTX *pCtx = (RTASN1OCTETSTRINGWRITERCTX *)pvUser; + AssertReturn(cbToWrite <= pCtx->cbBuf - pCtx->offBuf, + RTErrInfoSetF(pErrInfo, VERR_BUFFER_OVERFLOW, + "cbToWrite=%#x offBuf=%#x cbBuf=%#x", cbToWrite, pCtx->cbBuf, pCtx->offBuf)); + memcpy(&pCtx->pbBuf[pCtx->offBuf], pvBuf, cbToWrite); + pCtx->offBuf += (uint32_t)cbToWrite; + return VINF_SUCCESS; +} + + +/** @callback_method_impl{FNRTASN1ENCODEWRITER, + * Used to compare the encoded raw content of an octet or bit string with the + * encapsulated object. } */ +static DECLCALLBACK(int) rtAsn1OctetStringEncodeCompare(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo) +{ + RTASN1OCTETSTRINGWRITERCTX *pCtx = (RTASN1OCTETSTRINGWRITERCTX *)pvUser; + RT_NOREF_PV(pErrInfo); + AssertReturn(cbToWrite <= pCtx->cbBuf - pCtx->offBuf, VERR_BUFFER_OVERFLOW); + if (memcmp(&pCtx->pbBuf[pCtx->offBuf], pvBuf, cbToWrite) != 0) + return VERR_NOT_EQUAL; + pCtx->offBuf += (uint32_t)cbToWrite; + return VINF_SUCCESS; +} + + +/* + * ASN.1 OCTET STRING - Specific Methods + */ + +RTDECL(int) RTAsn1OctetString_RefreshContent(PRTASN1OCTETSTRING pThis, uint32_t fFlags, + PCRTASN1ALLOCATORVTABLE pAllocator, PRTERRINFO pErrInfo) +{ + AssertReturn(pThis->pEncapsulated, VERR_INVALID_STATE); + + uint32_t cbEncoded; + int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, pErrInfo); + if (RT_SUCCESS(rc)) + { + pThis->Asn1Core.cb = cbEncoded; + + rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, cbEncoded, pAllocator); + if (RT_SUCCESS(rc)) + { + /* Initialize the writer context. */ + RTASN1OCTETSTRINGWRITERCTX Ctx; + Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8; + Ctx.cbBuf = cbEncoded; + Ctx.offBuf = 0; + + rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1OctetStringEncodeWriter, &Ctx, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (Ctx.offBuf == cbEncoded) + return VINF_SUCCESS; + + rc = RTErrInfoSetF(pErrInfo, rc, "Expected %#x bytes, got %#x", cbEncoded, Ctx.offBuf); + } + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "Error allocating %#x bytes for storing content\n", cbEncoded); + } + return rc; +} + + +RTDECL(int) RTAsn1OctetString_AllocContent(PRTASN1OCTETSTRING pThis, void const *pvSrc, size_t cb, + PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertReturn(!pThis->pEncapsulated, VERR_INVALID_STATE); + int rc; + if (pvSrc) + rc = RTAsn1ContentDup(&pThis->Asn1Core, pvSrc, cb, pAllocator); + else + rc = RTAsn1ContentAllocZ(&pThis->Asn1Core, cb, pAllocator); + return rc; +} + + +RTDECL(int) RTAsn1OctetString_SetContent(PRTASN1OCTETSTRING pThis, void const *pvSrc, size_t cbSrc, + PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtrReturn(pvSrc, VERR_INVALID_POINTER); + return RTAsn1OctetString_AllocContent(pThis, pvSrc, cbSrc, pAllocator); +} + + +RTDECL(bool) RTAsn1OctetString_AreContentBytesValid(PCRTASN1OCTETSTRING pThis, uint32_t fFlags) +{ + if (pThis->pEncapsulated) + { + /* Check the encoded length of the octets. */ + uint32_t cbEncoded; + int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, NULL); + if (RT_FAILURE(rc)) + return false; + if (pThis->Asn1Core.cb != cbEncoded) + return false; + + /* Check the encoded bytes, if there are any. */ + if (cbEncoded) + { + if (!pThis->Asn1Core.uData.pv) + return false; + + /* Check the other bytes. */ + RTASN1OCTETSTRINGWRITERCTX Ctx; + Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8; + Ctx.cbBuf = cbEncoded; + Ctx.offBuf = 0; + rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1OctetStringEncodeCompare, &Ctx, NULL); + if (RT_FAILURE(rc)) + return false; + } + } + return true; +} + + +/* + * ASN.1 OCTET STRING - Standard Methods. + */ + +/** @interface_method_impl{FNRTASN1COREVTENCODEPREP} */ +static DECLCALLBACK(int) RTAsn1OctetString_EncodePrep(PRTASN1CORE pThisCore, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + PRTASN1OCTETSTRING pThis = (PRTASN1OCTETSTRING)pThisCore; + if (!pThis->pEncapsulated) + { + Assert(pThis->Asn1Core.cb == 0 || pThis->Asn1Core.uData.pv); + return VINF_SUCCESS; + } + + /* Figure out the size of the encapsulated content. */ + uint32_t cbEncoded; + int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* Free the bytes if they don't match up. */ + if (pThis->Asn1Core.uData.pv) + { + bool fMustFree = pThis->Asn1Core.cb != cbEncoded; + if (!fMustFree) + { + RTASN1OCTETSTRINGWRITERCTX Ctx; + Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8; + Ctx.cbBuf = cbEncoded; + Ctx.offBuf = 0; + rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1OctetStringEncodeCompare, &Ctx, NULL); + fMustFree = RT_FAILURE_NP(rc); + } + if (fMustFree) + RTAsn1ContentFree(&pThis->Asn1Core); + } + + pThis->Asn1Core.cb = cbEncoded; + rc = RTAsn1EncodeRecalcHdrSize(&pThis->Asn1Core, fFlags, pErrInfo); + } + return rc; +} + + +/** @interface_method_impl{FNRTASN1COREVTENCODEWRITE} */ +static DECLCALLBACK(int) RTAsn1OctetString_EncodeWrite(PRTASN1CORE pThisCore, uint32_t fFlags, PFNRTASN1ENCODEWRITER pfnWriter, + void *pvUser, PRTERRINFO pErrInfo) +{ + PRTASN1OCTETSTRING pThis = (PRTASN1OCTETSTRING)pThisCore; + + /* + * First the header. + */ + int rc = RTAsn1EncodeWriteHeader(&pThis->Asn1Core, fFlags, pfnWriter, pvUser, pErrInfo); + if (RT_SUCCESS(rc) && rc != VINF_ASN1_NOT_ENCODED) + { + /* + * If nothing is encapsulated, the core points to the content (if we have any). + */ + if (!pThis->pEncapsulated) + { + if (pThis->Asn1Core.cb > 0) + rc = pfnWriter(pThis->Asn1Core.uData.pu8, pThis->Asn1Core.cb, pvUser, pErrInfo); + } + /* + * Call upon the encapsulated content to serialize itself. + */ + else + rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, pfnWriter, pvUser, pErrInfo); + } + return rc; +} + + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1OctetString_Vtable = +{ + "OctetString", + sizeof(RTASN1OCTETSTRING), + ASN1_TAG_OCTET_STRING, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + 0, + (PFNRTASN1COREVTDTOR)RTAsn1OctetString_Delete, + (PFNRTASN1COREVTENUM)RTAsn1OctetString_Enum, + (PFNRTASN1COREVTCLONE)RTAsn1OctetString_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1OctetString_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1OctetString_CheckSanity, + RTAsn1OctetString_EncodePrep, + RTAsn1OctetString_EncodeWrite +}; + + +RTDECL(int) RTAsn1OctetString_Init(PRTASN1OCTETSTRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_ZERO(*pThis); + + RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_OCTET_STRING, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1OctetString_Vtable, RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + /*pThis->pEncapsulated = NULL;*/ + RTAsn1MemInitAllocation(&pThis->EncapsulatedAllocation, pAllocator); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1OctetString_Clone(PRTASN1OCTETSTRING pThis, PCRTASN1OCTETSTRING pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); + + RT_ZERO(*pThis); + if (RTAsn1OctetString_IsPresent(pSrc)) + { + AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable, VERR_INTERNAL_ERROR_3); + + int rc; + if (!pSrc->pEncapsulated) + rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator); + else + rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core); + if (RT_FAILURE(rc)) + return rc; + + RTAsn1MemInitAllocation(&pThis->EncapsulatedAllocation, pAllocator); + if (pSrc->pEncapsulated) + { + PCRTASN1COREVTABLE pOps = pSrc->pEncapsulated->pOps; + Assert(!pOps || pOps->pfnClone); + if (pOps && pOps->pfnClone) + { + /* We can clone the decoded encapsulated object. */ + rc = RTAsn1MemAllocZ(&pThis->EncapsulatedAllocation, (void **)&pThis->pEncapsulated, pOps->cbStruct); + if (RT_SUCCESS(rc)) + { + rc = pOps->pfnClone(pThis->pEncapsulated, pSrc->pEncapsulated, pAllocator); + if (RT_FAILURE(rc)) + RTAsn1MemFree(&pThis->EncapsulatedAllocation, pThis->pEncapsulated); + } + } + else + { + /* Borrow the encapsulated pointer and use RTAsn1OctetString_RefreshContent + to get an accurate copy of the bytes. */ + pThis->pEncapsulated = pSrc->pEncapsulated; + rc = RTAsn1OctetString_RefreshContent(pThis, RTASN1ENCODE_F_DER, pAllocator, NULL); + pThis->pEncapsulated = NULL; + } + if (RT_FAILURE(rc)) + { + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + return rc; + } + } + } + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1OctetString_Delete(PRTASN1OCTETSTRING pThis) +{ + if ( pThis + && RTAsn1OctetString_IsPresent(pThis)) + { + Assert(pThis->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable); + + /* Destroy the encapsulated object. */ + if (pThis->pEncapsulated) + { + RTAsn1VtDelete(pThis->pEncapsulated); + if (pThis->EncapsulatedAllocation.cbAllocated) + RTAsn1MemFree(&pThis->EncapsulatedAllocation, pThis->pEncapsulated); + } + + /* Delete content and wipe the content. */ + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1OctetString_Enum(PRTASN1OCTETSTRING pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + Assert(pThis && (!RTAsn1OctetString_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable)); + + /* Enumerate the encapsulated object if present. */ + if (pThis->pEncapsulated) + return pfnCallback(pThis->pEncapsulated, "Encapsulated", uDepth + 1, pvUser); + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1OctetString_Compare(PCRTASN1OCTETSTRING pLeft, PCRTASN1OCTETSTRING pRight) +{ + Assert(pLeft && (!RTAsn1OctetString_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable)); + Assert(pRight && (!RTAsn1OctetString_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable)); + + int iDiff; + if (RTAsn1OctetString_IsPresent(pLeft)) + { + if (RTAsn1OctetString_IsPresent(pRight)) + { + /* Since it's really hard to tell whether encapsulated objects have + been modified or not, we might have to refresh both objects + while doing this compare. We'll try our best to avoid it though. */ + if (pLeft->pEncapsulated || pRight->pEncapsulated) + { + if ( pLeft->pEncapsulated + && pRight->pEncapsulated + && pLeft->pEncapsulated->pOps == pRight->pEncapsulated->pOps) + iDiff = pLeft->pEncapsulated->pOps->pfnCompare(pLeft->pEncapsulated, pRight->pEncapsulated); + else + { + /* No direct comparison of encapsulated objects possible, + make sure we've got the rigth bytes then. */ + if ( pLeft->pEncapsulated + && !RTAsn1OctetString_AreContentBytesValid(pLeft, RTASN1ENCODE_F_DER)) + { + int rc = RTAsn1OctetString_RefreshContent((PRTASN1OCTETSTRING)pLeft, RTASN1ENCODE_F_DER, + pLeft->EncapsulatedAllocation.pAllocator, NULL); + AssertRC(rc); + } + + if ( pRight->pEncapsulated + && !RTAsn1OctetString_AreContentBytesValid(pRight, RTASN1ENCODE_F_DER)) + { + int rc = RTAsn1OctetString_RefreshContent((PRTASN1OCTETSTRING)pRight, RTASN1ENCODE_F_DER, + pRight->EncapsulatedAllocation.pAllocator, NULL); + AssertRC(rc); + } + + /* Compare the content bytes. */ + iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/); + } + } + /* + * No encapsulated object, just compare the raw content bytes. + */ + else + iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/); + } + else + iDiff = -1; + } + else + iDiff = 0 - (int)RTAsn1OctetString_IsPresent(pRight); + return iDiff; +} + + +RTDECL(int) RTAsn1OctetString_CheckSanity(PCRTASN1OCTETSTRING pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + if (RT_UNLIKELY(!RTAsn1OctetString_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (OCTET STRING).", pszErrorTag); + + if (pThis->pEncapsulated) + return pThis->pEncapsulated->pOps->pfnCheckSanity(pThis->pEncapsulated, fFlags & RTASN1_CHECK_SANITY_F_COMMON_MASK, + pErrInfo, pszErrorTag); + return VINF_SUCCESS; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-octetstring-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-core.h> +#include <iprt/asn1-generator-init.h> +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-string-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-string-decode.cpp new file mode 100644 index 00000000..d9444d1c --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-string-decode.cpp @@ -0,0 +1,199 @@ +/* $Id: asn1-ut-string-decode.cpp $ */ +/** @file + * IPRT - ASN.1, XXX STRING Types, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/alloca.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/ctype.h> + +#include <iprt/formats/asn1.h> + + +RTDECL(int) RTAsn1String_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pThis, const char *pszErrorTag) +{ + RT_ZERO(*pThis); + AssertReturn(!(fFlags & RTASN1CURSOR_GET_F_IMPLICIT), VERR_INVALID_PARAMETER); + + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + /* + * Do tag matching. + */ + switch (pThis->Asn1Core.uTag) + { + case ASN1_TAG_UTF8_STRING: + case ASN1_TAG_NUMERIC_STRING: + case ASN1_TAG_PRINTABLE_STRING: + case ASN1_TAG_T61_STRING: + case ASN1_TAG_VIDEOTEX_STRING: + case ASN1_TAG_IA5_STRING: + case ASN1_TAG_GENERALIZED_TIME: + case ASN1_TAG_GRAPHIC_STRING: + case ASN1_TAG_VISIBLE_STRING: + case ASN1_TAG_GENERAL_STRING: + case ASN1_TAG_UNIVERSAL_STRING: + case ASN1_TAG_BMP_STRING: + rc = VINF_SUCCESS; + break; + default: + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TAG_MISMATCH, + "%s: Not a string object: fClass=%#x / uTag=%#x", + pszErrorTag, pThis->Asn1Core.fClass, pThis->Asn1Core.uTag); + } + if (RT_SUCCESS(rc)) + { + /* + * Match flags. CER/DER makes it complicated. + */ + if (pThis->Asn1Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE)) + { + /* + * Primitive strings are simple. + */ + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1String_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + RTAsn1CursorInitAllocation(pCursor, &pThis->Allocation); + + /* UTF-8 conversion is done lazily, upon request. */ + return VINF_SUCCESS; + } + + if (pThis->Asn1Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED)) + { + /* + * Constructed strings are not yet fully implemented. + */ + if (pCursor->fFlags & RTASN1CURSOR_FLAGS_DER) + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING, + "%s: DER encoding does not allow constructed strings (cb=%#x uTag=%#x fClass=%#x)", + pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core.uTag, pThis->Asn1Core.fClass); + else if (pCursor->fFlags & RTASN1CURSOR_FLAGS_CER) + { + if (pThis->Asn1Core.cb > 1000) + rc = VINF_SUCCESS; + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING, + "%s: Constructed strings only allowed for >1000 byte in CER encoding: cb=%#x uTag=%#x fClass=%#x", + pszErrorTag, pThis->Asn1Core.cb, + pThis->Asn1Core.uTag, pThis->Asn1Core.fClass); + } + /** @todo implement constructed strings. */ + if (RT_SUCCESS(rc)) + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CONSTRUCTED_STRING_NOT_IMPL, + "%s: Support for constructed strings is not implemented", pszErrorTag); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TAG_FLAG_CLASS_MISMATCH, + "%s: Not a valid string object: fClass=%#x / uTag=%#x", + pszErrorTag, pThis->Asn1Core.fClass, pThis->Asn1Core.uTag); + } + } + RT_ZERO(*pThis); + return rc; +} + + +/** + * Common worker for the specific string type getters. + * + * @returns IPRT status code + * @param pCursor The cursor. + * @param fFlags The RTASN1CURSOR_GET_F_XXX flags. + * @param uTag The string tag. + * @param pThis The output object. + * @param pszErrorTag The error tag. + * @param pszWhat The string type name. + */ +static int rtAsn1XxxString_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, uint8_t uTag, PRTASN1STRING pThis, + const char *pszErrorTag, const char *pszWhat) +{ + pThis->cchUtf8 = 0; + pThis->pszUtf8 = NULL; + + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlagsString(pCursor, &pThis->Asn1Core, uTag, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + fFlags, pszErrorTag, pszWhat); + if (RT_SUCCESS(rc)) + { + if (!(pThis->Asn1Core.fClass & ASN1_TAGFLAG_CONSTRUCTED)) + { + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1String_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + RTAsn1CursorInitAllocation(pCursor, &pThis->Allocation); + /* UTF-8 conversion is done lazily, upon request. */ + return VINF_SUCCESS; + } + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CONSTRUCTED_STRING_NOT_IMPL, + "%s: Constructed %s not implemented.", pszErrorTag, pszWhat); + } + } + RT_ZERO(*pThis); + return rc; +} + + +/* + * Generate code for the tag specific decoders. + */ +#define RTASN1STRING_IMPL(a_uTag, a_szTag, a_Api) \ + RTDECL(int) RT_CONCAT(a_Api,_DecodeAsn1)(PRTASN1CURSOR pCursor, uint32_t fFlags, \ + PRTASN1STRING pThis, const char *pszErrorTag) \ + { \ + return rtAsn1XxxString_DecodeAsn1(pCursor, fFlags, a_uTag, pThis, pszErrorTag, a_szTag); \ + } +#include "asn1-ut-string-template2.h" + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-string-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-string-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-string-template.h new file mode 100644 index 00000000..cb1f98b8 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-string-template.h @@ -0,0 +1,55 @@ +/* $Id: asn1-ut-string-template.h $ */ +/** @file + * IPRT - ASN.1, XXX STRING Types, Collection Type Template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +#define RTASN1TMPL_TYPE RTASN1SEQOFSTRINGS +#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfStrings +#define RTASN1TMPL_INT_NAME rtAsn1SeqOfStrings +RTASN1TMPL_SEQ_OF(RTASN1STRING, RTAsn1String); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFSTRINGS +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfStrings +#define RTASN1TMPL_INT_NAME rtAsn1SetOfStrings +RTASN1TMPL_SET_OF(RTASN1STRING, RTAsn1String); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-string-template2.h b/src/VBox/Runtime/common/asn1/asn1-ut-string-template2.h new file mode 100644 index 00000000..217fe3d8 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-string-template2.h @@ -0,0 +1,49 @@ +/* $Id: asn1-ut-string-template2.h $ */ +/** @file + * IPRT - ASN.1, XXX STRING Types, Template for type specific wrappers. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +RTASN1STRING_IMPL(ASN1_TAG_NUMERIC_STRING, "NUMERIC STRING", RTAsn1NumericString) +RTASN1STRING_IMPL(ASN1_TAG_PRINTABLE_STRING, "PRINTABLE STRING", RTAsn1PrintableString) +RTASN1STRING_IMPL(ASN1_TAG_T61_STRING, "T61 STRING", RTAsn1T61String) +RTASN1STRING_IMPL(ASN1_TAG_VIDEOTEX_STRING, "VIDEOTEX STRING", RTAsn1VideotexString) +RTASN1STRING_IMPL(ASN1_TAG_VISIBLE_STRING, "VISIBLE STRING", RTAsn1VisibleString) +RTASN1STRING_IMPL(ASN1_TAG_IA5_STRING, "IA5 STRING", RTAsn1Ia5String) +RTASN1STRING_IMPL(ASN1_TAG_GRAPHIC_STRING, "GRAPHIC STRING", RTAsn1GraphicString) +RTASN1STRING_IMPL(ASN1_TAG_GENERAL_STRING, "GENERAL STRING", RTAsn1GeneralString) +RTASN1STRING_IMPL(ASN1_TAG_UTF8_STRING, "UTF8 STRING", RTAsn1Utf8String) +RTASN1STRING_IMPL(ASN1_TAG_BMP_STRING, "BMP STRING", RTAsn1BmpString) +RTASN1STRING_IMPL(ASN1_TAG_UNIVERSAL_STRING, "UNIVERSAL STRING", RTAsn1UniversalString) + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-string.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-string.cpp new file mode 100644 index 00000000..edec7957 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-string.cpp @@ -0,0 +1,1855 @@ +/* $Id: asn1-ut-string.cpp $ */ +/** @file + * IPRT - ASN.1, XXX STRING Types. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static uint8_t const g_acbStringTags[] = +{ + /* [ASN1_TAG_EOC] = */ 0, + /* [ASN1_TAG_BOOLEAN] = */ 0, + /* [ASN1_TAG_INTEGER] = */ 0, + /* [ASN1_TAG_BIT_STRING] = */ 0, + /* [ASN1_TAG_OCTET_STRING] = */ 0, + /* [ASN1_TAG_NULL] = */ 0, + /* [ASN1_TAG_OID] = */ 0, + /* [ASN1_TAG_OBJECT_DESCRIPTOR] = */ 0, + /* [ASN1_TAG_EXTERNAL] = */ 0, + /* [ASN1_TAG_REAL] = */ 0, + /* [ASN1_TAG_ENUMERATED] = */ 0, + /* [ASN1_TAG_EMBEDDED_PDV] = */ 0, + /* [ASN1_TAG_UTF8_STRING] = */ 1, + /* [ASN1_TAG_RELATIVE_OID] = */ 0, + /* [ASN1_TAG_RESERVED_14] = */ 0, + /* [ASN1_TAG_RESERVED_15] = */ 0, + /* [ASN1_TAG_SEQUENCE] = */ 0, + /* [ASN1_TAG_SET] = */ 0, + /* [ASN1_TAG_NUMERIC_STRING] = */ 1, + /* [ASN1_TAG_PRINTABLE_STRING] = */ 1, + /* [ASN1_TAG_T61_STRING] = */ 1, + /* [ASN1_TAG_VIDEOTEX_STRING] = */ 1, + /* [ASN1_TAG_IA5_STRING] = */ 1, + /* [ASN1_TAG_UTC_TIME] = */ 0, + /* [ASN1_TAG_GENERALIZED_TIME] = */ 0, + /* [ASN1_TAG_GRAPHIC_STRING] = */ 1, + /* [ASN1_TAG_VISIBLE_STRING] = */ 1, + /* [ASN1_TAG_GENERAL_STRING] = */ 1, + /* [ASN1_TAG_UNIVERSAL_STRING] = */ 4, + /* [ASN1_TAG_CHARACTER_STRING] = */ 1, + /* [ASN1_TAG_BMP_STRING] = */ 2, +}; + + + + +/* + * ISO/IEC-2022 + TeletexString mess. + */ + +/** + * ISO-2022 codepoint mappings. + */ +typedef struct RTISO2022MAP +{ + /** The number of bytes per character. */ + uint8_t cb; + /** The registration number. */ + uint16_t uRegistration; + /** The size of the pauToUni table. */ + uint16_t cToUni; + /** Pointer to the convertion table from ISO-2022 to Unicode. + * ASSUMES that unicode chars above 0xffff won't be required. */ + uint16_t const *pauToUni; + + /** Escape sequence for loading into G0 or C0 or C1 depending on the type (sans + * ESC). */ + uint8_t abEscLoadXX[6]; + /** Escape sequence for loading into G1 (sans ESC). */ + uint8_t abEscLoadG1[6]; + /** Escape sequence for loading into G2 (sans ESC). */ + uint8_t abEscLoadG2[6]; + /** Escape sequence for loading into G3 (sans ESC). */ + uint8_t abEscLoadG3[6]; +} RTISO2022MAP; +/** Pointer to const ISO-2022 mappings. */ +typedef RTISO2022MAP const *PCRTISO2022MAP; + +/** Unused codepoint value. */ +#define RTISO2022_UNUSED UINT16_C(0xffff) + + +/** Dummy mappings to avoid dealing with NULL pointers in the decoder + * registers. */ +static const RTISO2022MAP g_DummyMap = +{ + 1, UINT16_MAX, 0, NULL, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* No escape into G0 */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* No escape into G1 */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* No escape into G2 */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* No escape into G3 */ +}; + + +/** GL mappings for ISO-IR-168 (Japanese, update of #87), with space and + * delete. */ +static const RTISO2022MAP g_IsoIr168Map = +{ + //2, 168, RT_ELEMENTS(g_awcIsoIr168Decode), g_awcIsoIr168Decode, + 2, 168, 0, NULL, + { 0x26, 0x40, 0x2b, 0x24, 0x42, 0xff } /* Esc into G0 */, + { 0x26, 0x40, 0x2b, 0x24, 0x29, 0x42 } /* Esc into G1 */, + { 0x26, 0x40, 0x2b, 0x24, 0x2a, 0x42 } /* Esc into G2 */, + { 0x26, 0x40, 0x2b, 0x24, 0x2b, 0x42 } /* Esc into G3 */, +}; + + +/** GL mappings for ISO-IR-165 (Chinese), with space and delete. */ +static const RTISO2022MAP g_IsoIr165Map = +{ + //2, 165, RT_ELEMENTS(g_awcIsoIr165Decode), g_awcIsoIr165Decode, + 2, 165, 0, NULL, + { 0x24, 0x28, 0x45, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x24, 0x29, 0x45, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x24, 0x2a, 0x45, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x24, 0x2b, 0x45, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** GL mappings for ISO-IR-150 (Greek), with space and delete. */ +static const RTISO2022MAP g_IsoIr150Map = +{ + //1, 150, RT_ELEMENTS(g_awcIsoIr150Decode), g_awcIsoIr150Decode, + 1, 150, 0, NULL, + { 0x28, 0x21, 0x40, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x29, 0x21, 0x40, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x2a, 0x21, 0x40, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x2b, 0x21, 0x40, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** GL mappings for ISO-IR-103 (Teletex supplementary), with space and + * delete. */ +static const RTISO2022MAP g_IsoIr103Map = +{ + //1, 103, RT_ELEMENTS(g_awcIsoIr103Decode), g_awcIsoIr103Decode, + 1, 103, 0, NULL, + { 0x28, 0x76, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x29, 0x76, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x2a, 0x76, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x2b, 0x76, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** + * GL mapping from ISO-IR-102 (Teletex primary) to unicode, with space and + * delete. + * + * Mostly 1:1, except that (a) what would be dollar is currency sign, (b) + * positions 0x5c, 0x5e, 0x7b, 0x7d and 0x7e are defined not to be used. + */ +static uint16_t const g_awcIsoIr102Decode[0x60] = +{ + 0x0020, 0x0021, 0x0022, 0x0023, 0x00A4, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0xffff, 0x005d, 0xffff, 0x005f, + 0xffff, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0xffff, 0x007c, 0xffff, 0xffff, 0x007f, +}; + +/** GL mappings for ISO-IR-102, with space and delete. */ +static const RTISO2022MAP g_IsoIr102Map = +{ + 1, 102, RT_ELEMENTS(g_awcIsoIr102Decode), g_awcIsoIr102Decode, + { 0x28, 0x75, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x29, 0x75, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x2a, 0x75, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x2b, 0x75, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +#if 0 /* unused */ +/** GL mappings for ISO-IR-87 (Japanese), with space and delete. */ +static const RTISO2022MAP g_IsoIr87Map = +{ + //1, 87, RT_ELEMENTS(g_awcIsoIr87Decode), g_awcIsoIr97Decode, + 1, 87, 0, NULL, + { 0x24, 0x42, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x24, 0x29, 0x42, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x24, 0x2a, 0x42, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x24, 0x2b, 0x42, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; +#endif + + +/** + * GL mapping from ISO-IR-6 (ASCII) to unicode, with space and delete. + * + * Completely 1:1. + */ +static uint16_t const g_awcIsoIr6Decode[0x60] = +{ + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x006b, 0x007c, 0x007d, 0x007e, 0x007f, +}; + +/** GL mappings for ISO-IR-6 (ASCII), with space and delete. */ +static const RTISO2022MAP g_IsoIr6Map = +{ + 1, 6, RT_ELEMENTS(g_awcIsoIr6Decode), g_awcIsoIr6Decode, + { 0x28, 0x42, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x29, 0x42, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** GL maps. */ +static PCRTISO2022MAP g_paGLMaps[] = +{ + &g_IsoIr6Map, + &g_IsoIr102Map, + &g_IsoIr103Map, + &g_IsoIr150Map, + &g_IsoIr165Map, + &g_IsoIr168Map, +}; + + + +/** GR mappings for ISO-IR-164 (Hebrew supplementary). */ +static const RTISO2022MAP g_IsoIr164Map = +{ + //1, 164, RT_ELEMENTS(g_awcIsoIr164Decode), g_awcIsoIr164Decode + 1, 164, 0, NULL, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x2d, 0x53, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x2e, 0x53, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x2f, 0x53, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** GR mappings for ISO-IR-156 (Supplementary for ASCII (#6)). */ +static const RTISO2022MAP g_IsoIr156Map = +{ + //1, 156, RT_ELEMENTS(g_awcIsoIr156Decode), g_awcIsoIr156Decode + 1, 156, 0, NULL, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x2d, 0x52, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x2e, 0x52, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x2f, 0x52, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** GR mappings for ISO-IR-153 (Basic Cyrillic). */ +static const RTISO2022MAP g_IsoIr153Map = +{ + //1, 153, RT_ELEMENTS(g_awcIsoIr153Decode), g_awcIsoIr153Decode + 1, 153, 0, NULL, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x2d, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x2e, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x2f, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** GR mappings for ISO-IR-144 (Cryllic part of Latin/Cyrillic). */ +static const RTISO2022MAP g_IsoIr144Map = +{ + //1, 144, RT_ELEMENTS(g_awcIsoIr144Decode), g_awcIsoIr144Decode + 1, 144, 0, NULL, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x2d, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x2e, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x2f, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** GR mappings for ISO-IR-126 (Latin/Greek). */ +static const RTISO2022MAP g_IsoIr126Map = +{ + //1, 126, RT_ELEMENTS(g_awcIsoIr126Decode), g_awcIsoIr126Decode + 1, 126, 0, NULL, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */, + { 0x2d, 0x46, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */, + { 0x2e, 0x46, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */, + { 0x2f, 0x46, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */, +}; + + +/** GR maps. */ +static PCRTISO2022MAP g_paGRMaps[] = +{ + &g_IsoIr126Map, + &g_IsoIr144Map, + &g_IsoIr153Map, + &g_IsoIr156Map, + &g_IsoIr164Map, +}; + + + +/** C0 mapping from ISO-IR-106 to unicode. */ +static uint16_t g_awcIsoIr106Decode[0x20] = +{ + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0008, 0xffff, 0x000a, 0xffff, 0x000c, 0x000d, 0x000e, 0x000f, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x008e, 0x000a, 0x001b, 0xffff, 0x008f, 0xffff, 0xffff, +}; + +/** C0 mappings for ISO-IR-106. */ +static const RTISO2022MAP g_IsoIr106Map = +{ + 1, 106, RT_ELEMENTS(g_awcIsoIr106Decode), g_awcIsoIr106Decode, + { 0x21, 0x45, 0xff, 0xff, 0xff, 0xff } /* Esc into C0 */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */, +}; + +/** C0 maps. */ +static PCRTISO2022MAP g_paC0Maps[] = +{ + &g_IsoIr106Map, +}; + + + +/** C1 mapping from ISO-IR-107 to unicode. */ +static uint16_t g_awcIsoIr107Decode[0x20] = +{ + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x008b, 0x008c, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x009b, 0xffff, 0xffff, 0xffff, 0xffff, +}; + +/** C1 mappings for ISO-IR-107. */ +static const RTISO2022MAP g_IsoIr107Map = +{ + 1, 107, RT_ELEMENTS(g_awcIsoIr107Decode), g_awcIsoIr107Decode, + { 0x22, 0x48, 0xff, 0xff, 0xff, 0xff } /* Esc into C1 */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */, +}; + +/** C1 maps. */ +static PCRTISO2022MAP g_paC1Maps[] = +{ + &g_IsoIr107Map, +}; + + +static int rtIso2022Decoder_LookupAndSet(PCRTISO2022MAP *ppMapRet, uint16_t uRegistration, PCRTISO2022MAP *papMaps, uint32_t cMaps) +{ + uint32_t i = cMaps; + while (i-- > 0) + if (papMaps[i]->uRegistration == uRegistration) + { + /** @todo skip non-Teletex codesets if we ever add more than we need for it. */ + *ppMapRet = papMaps[i]; + return VINF_SUCCESS; + } + return VERR_ASN1_INVALID_T61_STRING_ENCODING; +} + + +/** + * ISO-2022 decoder state. + */ +typedef struct RTISO2022DECODERSTATE +{ + /** Pointer to the string */ + uint8_t const *pabString; + /** The string size. */ + uint32_t cbString; + /** The current string position. */ + uint32_t offString; + + /** The GL mapping. */ + PCRTISO2022MAP pMapGL; + /** The GR mapping. */ + PCRTISO2022MAP pMapGR; + /** The lower control set (C0) mapping. */ + PCRTISO2022MAP pMapC0; + /** The higher control set (C1) mapping. */ + PCRTISO2022MAP pMapC1; + /** The G0, G1, G2, and G3 mappings. */ + PCRTISO2022MAP apMapGn[4]; + /** Used by SS2 & SS3 to store the orignal GL value that is to be restored. */ + PCRTISO2022MAP pRestoreGL; + /** Pointer to extended error info buffer, optional. */ + PRTERRINFO pErrInfo; +} RTISO2022DECODERSTATE; +/** Pointer to a const ISO-2022 decoder state. */ +typedef RTISO2022DECODERSTATE *PRTISO2022DECODERSTATE; + + +static int rtIso2022Decoder_SetGL(PRTISO2022DECODERSTATE pThis, PCRTISO2022MAP pNewMap) +{ + pThis->pMapGL = pNewMap; + return VINF_SUCCESS; +} + + +static int rtIso2022Decoder_SetGR(PRTISO2022DECODERSTATE pThis, PCRTISO2022MAP pNewMap) +{ + pThis->pMapGR = pNewMap; + return VINF_SUCCESS; +} + + +static int rtIso2022Decoder_SetGLForOneChar(PRTISO2022DECODERSTATE pThis, PCRTISO2022MAP pTmpMap) +{ + pThis->pRestoreGL = pThis->pMapGL; + pThis->pMapGL = pTmpMap; + return VINF_SUCCESS; +} + + +static int rtIso2022Decoder_SetC0(PRTISO2022DECODERSTATE pThis, uint16_t uRegistration) +{ + return rtIso2022Decoder_LookupAndSet(&pThis->pMapC0, uRegistration, g_paC0Maps, RT_ELEMENTS(g_paC0Maps)); +} + + +static int rtIso2022Decoder_SetC1(PRTISO2022DECODERSTATE pThis, uint16_t uRegistration) +{ + return rtIso2022Decoder_LookupAndSet(&pThis->pMapC1, uRegistration, g_paC1Maps, RT_ELEMENTS(g_paC1Maps)); +} + + +/** + * Worker for rtIso2022Decoder_FindEscAndSet. + * + * @returns true if match, false if not. + * @param pabLeft Pointer to the first string byte after the ESC. + * @param cbLeft The number of bytes left in the string. + * @param pabRight Pointer to the abEscLoad* byte array to match with. + * @param cbRight Size of the mapping sequence (fixed). + * @param pcchMatch Where to return the length of the escape sequence (sans + * ESC) on success. + */ +static bool rtIso2022Decoder_MatchEscSeqFrom2ndByte(uint8_t const *pabLeft, uint32_t cbLeft, + uint8_t const *pabRight, uint32_t cbRight, + uint32_t *pcchMatch) +{ + Assert(cbRight == 6); + uint32_t i = 1; + while (i < cbRight) + { + if (pabRight[i] == 0xff) + break; + if (cbLeft <= i || pabLeft[i] != pabRight[i]) + return false; + i++; + } + *pcchMatch = i; + return true; +} + + +/** + * Finds a the set with a matching abEscLoad* escape sequence and loads it into + * the designated register. + * + * @returns The length of the sequence on success, negative error status code on + * failure. + * @param pThis The decoder instance. + * @param ppMapRet Used to specify C0 or C1 maps when processing + * escape sequences for loading these. Only the + * abEscLoadXX arrays will be searched if this is + * not NULL. For loading {G0,...,G3} pass NULL. + * @param pb Pointer to the start of the escape sequence. + * @param cb The number of bytes remaining in the string. + * @param papMaps The maps to search. + * @param cMaps The number of maps @a papMaps points to. + */ +static int rtIso2022Decoder_FindEscAndSet(PRTISO2022DECODERSTATE pThis, + PCRTISO2022MAP *ppMapRet, PCRTISO2022MAP *papMaps, uint32_t cMaps) +{ + /* Skip the ESC.*/ + uint8_t const *pb = &pThis->pabString[pThis->offString + 1]; + uint32_t cb = pThis->cbString - (pThis->offString + 1); + + /* Cache the first char. */ + uint8_t const b0 = pb[0]; + + /* Scan the array of maps for matching sequences. */ + uint32_t i = cMaps; + while (i-- > 0) + { + uint32_t cchMatch = 0; /* (MSC maybe used uninitialized) */ + PCRTISO2022MAP pMap = papMaps[i]; + /** @todo skip non-Teletex codesets if we ever add more than we need for it. */ + if ( pMap->abEscLoadXX[0] == b0 + && rtIso2022Decoder_MatchEscSeqFrom2ndByte(pb, cb, pMap->abEscLoadXX, sizeof(pMap->abEscLoadXX), &cchMatch) ) + { + if (ppMapRet) + *ppMapRet = pMap; + else + pThis->apMapGn[0] = pMap; + return cchMatch + 1; + } + + if (!ppMapRet) /* ppMapRet is NULL if Gn. */ + { + uint32_t iGn; + if ( pMap->abEscLoadG1[0] == b0 + && rtIso2022Decoder_MatchEscSeqFrom2ndByte(pb, cb, pMap->abEscLoadG1, sizeof(pMap->abEscLoadG1), &cchMatch)) + iGn = 1; + else if ( pMap->abEscLoadG2[0] == b0 + && rtIso2022Decoder_MatchEscSeqFrom2ndByte(pb, cb, pMap->abEscLoadG2, sizeof(pMap->abEscLoadG2), &cchMatch)) + iGn = 2; + else if ( pMap->abEscLoadG3[0] == b0 + && rtIso2022Decoder_MatchEscSeqFrom2ndByte(pb, cb, pMap->abEscLoadG3, sizeof(pMap->abEscLoadG3), &cchMatch)) + iGn = 3; + else + iGn = UINT32_MAX; + if (iGn != UINT32_MAX) + { + pThis->apMapGn[iGn] = pMap; + return cchMatch + 1; + } + } + } + return VERR_ASN1_TELETEX_UNSUPPORTED_CHARSET; +} + + +/** + * Interprets an escape sequence. + * + * @returns The length of the sequence on success, negative error status code on + * failure. + * @param pThis The decoder instance. The offString must be + * pointing to the escape byte. + */ +static int rtIso2022Decoder_InterpretEsc(PRTISO2022DECODERSTATE pThis) +{ + /* the first escape byte. */ + uint32_t offString = pThis->offString; + if (offString + 1 >= pThis->cbString) + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: Unexpected EOS parsing ESC...", offString); + int rc; + switch (pThis->pabString[offString + 1]) + { + /* + * GL selection: + */ + case 0x6e: /* Lock shift two: G2 -> GL */ + rc = rtIso2022Decoder_SetGL(pThis, pThis->apMapGn[2]); + break; + case 0x6f: /* Lock shift three: G3 -> GL */ + rc = rtIso2022Decoder_SetGL(pThis, pThis->apMapGn[3]); + break; + case 0x4e: /* Single shift two: G2 -> GL for one char. */ + rc = rtIso2022Decoder_SetGLForOneChar(pThis, pThis->apMapGn[2]); + break; + case 0x4f: /* Single shift three: G3 -> GL for one char. */ + rc = rtIso2022Decoder_SetGLForOneChar(pThis, pThis->apMapGn[3]); + break; + + /* + * GR selection: + */ + case 0x7e: /* Locking shift one right: G1 -> GR. */ + rc = rtIso2022Decoder_SetGR(pThis, pThis->apMapGn[1]); + break; + case 0x7d: /* Locking shift two right: G2 -> GR. */ + rc = rtIso2022Decoder_SetGR(pThis, pThis->apMapGn[2]); + break; + case 0x7c: /* Locking shift three right: G3 -> GR. */ + rc = rtIso2022Decoder_SetGR(pThis, pThis->apMapGn[3]); + break; + + /* + * Cx selection: + */ + case 0x21: /* C0-designate */ + return rtIso2022Decoder_FindEscAndSet(pThis, &pThis->pMapC0, g_paC0Maps, RT_ELEMENTS(g_paC0Maps)); + case 0x22: /* C1-designate */ + return rtIso2022Decoder_FindEscAndSet(pThis, &pThis->pMapC1, g_paC1Maps, RT_ELEMENTS(g_paC1Maps)); + + /* + * Single-byte character set selection. + */ + case 0x28: /* G0-designate, 94 chars. */ + case 0x29: /* G1-designate, 94 chars. */ + case 0x2a: /* G2-designate, 94 chars. */ + case 0x2b: /* G3-designate, 94 chars. */ + return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGLMaps, RT_ELEMENTS(g_paGLMaps)); + + case 0x2c: /* G0-designate, 96 chars. */ + case 0x2d: /* G1-designate, 96 chars. */ + case 0x2e: /* G2-designate, 96 chars. */ + case 0x2f: /* G3-designate, 96 chars. */ + return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGRMaps, RT_ELEMENTS(g_paGRMaps)); + + /* + * Multibyte character set selection. + */ + case 0x24: + if (offString + 2 >= pThis->cbString) + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: Unexpected EOS parsing ESC %#x...", offString, pThis->pabString[offString + 1]); + switch (pThis->pabString[offString + 2]) + { + case 0x28: /* G0-designate, 94^n chars. */ + case 0x29: /* G1-designate, 94^n chars. */ + case 0x2a: /* G2-designate, 94^n chars. */ + case 0x2b: /* G3-designate, 94^n chars. */ + default: /* G0-designate that skips the 0x28? (See japanese ones.) */ + return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGLMaps, RT_ELEMENTS(g_paGLMaps)); + + case 0x2c: /* G0-designate, 96^n chars. */ + case 0x2d: /* G1-designate, 96^n chars. */ + case 0x2e: /* G2-designate, 96^n chars. */ + case 0x2f: /* G3-designate, 96^n chars. */ + return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGRMaps, RT_ELEMENTS(g_paGRMaps)); + } \ + break; + + case 0x26: /* Special escape prefix for #168. */ + return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGLMaps, RT_ELEMENTS(g_paGLMaps)); + + /* + * Unknown/unsupported/unimplemented. + */ + case 0x25: /* Designate other coding system. */ + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_TELETEX_UNSUPPORTED_ESC_SEQ, + "@%u: ESC DOCS not supported\n", offString); + default: + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_TELETEX_UNKNOWN_ESC_SEQ, + "@%u: Unknown escape sequence: ESC %#x...\n", offString, pThis->pabString[offString + 1]); + } + + /* Only single byte escapes sequences for shifting ends up here. */ + if (RT_SUCCESS(rc)) + return 1; + return rc; +} + + +static int rtIso2022Decoder_ControlCharHook(PRTISO2022DECODERSTATE pThis, uint16_t wcControl) +{ + int rc; + switch (wcControl) + { + case 0x000e: /* Locking shift zero: G0 -> GL. */ + rc = rtIso2022Decoder_SetGL(pThis, pThis->apMapGn[0]); + break; + + case 0x000f: /* Locking shift one: G1 -> GL. */ + rc = rtIso2022Decoder_SetGL(pThis, pThis->apMapGn[1]); + break; + + case 0x008e: /* Single shift two: G2 -> GL for one char. */ + rc = rtIso2022Decoder_SetGLForOneChar(pThis, pThis->apMapGn[2]); + break; + + case 0x008f: /* Single shift three: G3 -> GL for one char. */ + rc = rtIso2022Decoder_SetGLForOneChar(pThis, pThis->apMapGn[3]); + break; + + case 0x002b: /* Escape should be handled by the caller. */ + rc = rtIso2022Decoder_InterpretEsc(pThis); + break; + + default: + return 0; + } + + return RT_SUCCESS(rc) ? 1 : rc; +} + + +static int rtIso2022Decoder_Init(PRTISO2022DECODERSTATE pThis, const char *pchString, uint32_t cchString, + uint32_t uGL, uint32_t uC0, uint32_t uC1, uint32_t uG0, + PRTERRINFO pErrInfo) +{ + pThis->pabString = (uint8_t const *)pchString; + pThis->cbString = cchString; + pThis->offString = 0; + + pThis->pMapGL = &g_DummyMap; + pThis->pMapGR = &g_DummyMap; + pThis->pMapC0 = &g_DummyMap; + pThis->pMapC1 = &g_DummyMap; + pThis->pRestoreGL = NULL; + pThis->apMapGn[0] = &g_DummyMap; + pThis->apMapGn[1] = &g_DummyMap; + pThis->apMapGn[2] = &g_DummyMap; + pThis->apMapGn[3] = &g_DummyMap; + pThis->pErrInfo = pErrInfo; + + int rc = VINF_SUCCESS; + if (uGL != UINT32_MAX) + rc = rtIso2022Decoder_LookupAndSet(&pThis->pMapGL, uGL, g_paGLMaps, RT_ELEMENTS(g_paGLMaps)); + if (RT_SUCCESS(rc) && uG0 != UINT32_MAX) + rc = rtIso2022Decoder_LookupAndSet(&pThis->apMapGn[0], uG0, g_paGLMaps, RT_ELEMENTS(g_paGLMaps)); + if (RT_SUCCESS(rc) && uC0 != UINT32_MAX) + rc = rtIso2022Decoder_SetC0(pThis, uC0); + if (RT_SUCCESS(rc) && uC1 != UINT32_MAX) + rc = rtIso2022Decoder_SetC1(pThis, uC1); + return rc; +} + + +static int rtIso2022Decoder_GetNextUniCpSlow(PRTISO2022DECODERSTATE pThis, PRTUNICP pUniCp) +{ + while (pThis->offString < pThis->cbString) + { + uint8_t b = pThis->pabString[pThis->offString]; + if (!(b & 0x80)) + { + if (b >= 0x20) + { + /* + * GL range. + */ + b -= 0x20; + PCRTISO2022MAP pMap = pThis->pMapGL; + + /* Single byte character map. */ + if (pMap->cb == 1) + { + if (RT_LIKELY(b < pMap->cToUni)) + { + uint16_t wc = pMap->pauToUni[b]; + if (RT_LIKELY(wc != RTISO2022_UNUSED)) + { + *pUniCp = wc; + pThis->offString += 1; + return VINF_SUCCESS; + } + *pUniCp = RTUNICP_INVALID; + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: GL b=%#x is marked unused in map #%u range %u.", + pThis->offString, b + 0x20, pMap->uRegistration, pMap->cToUni); + } + *pUniCp = RTUNICP_INVALID; + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: GL b=%#x is outside map #%u range %u.", + pThis->offString, b + 0x20, pMap->uRegistration, pMap->cToUni); + } + + /* Double byte character set. */ + Assert(pMap->cb == 2); + if (pThis->offString + 1 < pThis->cbString) + { + uint8_t b2 = pThis->pabString[pThis->offString + 1]; + b2 -= 0x20; + if (RT_LIKELY(b2 < 0x60)) + { + uint16_t u16 = ((uint16_t)b << 8) | b2; + if (RT_LIKELY(u16 < pMap->cToUni)) + { + uint16_t wc = pMap->pauToUni[b]; + if (RT_LIKELY(wc != RTISO2022_UNUSED)) + { + *pUniCp = wc; + pThis->offString += 2; + return VINF_SUCCESS; + } + *pUniCp = RTUNICP_INVALID; + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: GL b=%#x is marked unused in map #%u.", + pThis->offString, b + 0x20, pMap->uRegistration); + } + if (u16 >= 0x7f00) + { + *pUniCp = 0x7f; /* delete */ + pThis->offString += 2; + return VINF_SUCCESS; + } + *pUniCp = RTUNICP_INVALID; + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: GL u16=%#x (b0=%#x b1=%#x) is outside map #%u range %u.", + pThis->offString, u16, b + 0x20, b2 + 0x20, pMap->uRegistration, pMap->cToUni); + } + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: 2nd GL byte outside GL range: b0=%#x b1=%#x (map #%u)", + pThis->offString, b + 0x20, b2 + 0x20, pMap->uRegistration); + + } + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: EOS reading 2nd byte for GL b=%#x (map #%u).", + pThis->offString, b + 0x20, pMap->uRegistration); + } + else + { + /* + * C0 range. + */ + Assert(pThis->pMapC0->cb == 0x20); + uint16_t wc = pThis->pMapC0->pauToUni[b]; + if (wc != RTISO2022_UNUSED) + { + int rc; + if (b == 0x1b || wc == 0x1b) /* ESC is hardcoded, or so they say. */ + rc = rtIso2022Decoder_InterpretEsc(pThis); + else + rc = rtIso2022Decoder_ControlCharHook(pThis, wc); + if (RT_SUCCESS(rc)) + { + if (rc == 0) + { + pThis->offString += 1; + *pUniCp = wc; + return VINF_SUCCESS; + } + pThis->offString += rc; + } + else + return rc; + } + else + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: C0 b=%#x is marked unused in map #%u.", + pThis->offString, b, pThis->pMapC0->uRegistration); + } + } + else + { + if (b >= 0xa0) + { + /* + * GR range. + */ + b -= 0xa0; + PCRTISO2022MAP pMap = pThis->pMapGR; + + /* Single byte character map. */ + if (pMap->cb == 1) + { + /** @todo 0xa0 = SPACE and 0xff = DELETE if it's a 94 charater map... */ + if (RT_LIKELY(b < pMap->cToUni)) + { + uint16_t wc = pMap->pauToUni[b]; + if (RT_LIKELY(wc != RTISO2022_UNUSED)) + { + *pUniCp = wc; + pThis->offString += 1; + return VINF_SUCCESS; + } + + *pUniCp = RTUNICP_INVALID; + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: GR b=%#x is marked unused in map #%u.", + pThis->offString, b + 0xa0, pMap->uRegistration); + } + *pUniCp = RTUNICP_INVALID; + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: GR b=%#x is outside map #%u range %u", + pThis->offString, b + 0xa0, pMap->uRegistration, pMap->cToUni); + } + + /* Double byte character set. */ + Assert(pMap->cb == 2); + if (pThis->offString + 1 < pThis->cbString) + { + uint8_t b2 = pThis->pabString[pThis->offString + 1]; + b2 -= 0xa0; + if (RT_LIKELY(b2 < 0x60)) + { + uint16_t u16 = ((uint16_t)b << 8) | b2; + if (RT_LIKELY(u16 < pMap->cToUni)) + { + uint16_t wc = pMap->pauToUni[b]; + if (RT_LIKELY(wc != RTISO2022_UNUSED)) + { + *pUniCp = wc; + pThis->offString += 2; + return VINF_SUCCESS; + } + + *pUniCp = RTUNICP_INVALID; + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: GR b=%#x is marked unused in map #%u.", + pThis->offString, b + 0xa0, pMap->uRegistration); + } + *pUniCp = RTUNICP_INVALID; + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: GR u16=%#x (b0=%#x b1=%#x) is outside map #%u range %u.", + pThis->offString, u16, b + 0xa0, b2 + 0xa0, pMap->uRegistration, pMap->cToUni); + } + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: 2nd GR byte outside GR range: b0=%#x b1=%#x (map #%u).", + pThis->offString, b + 0xa0, b2 + 0xa0, pMap->uRegistration); + + } + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: EOS reading 2nd byte for GR b=%#x (map #%u).", + pThis->offString, b + 0xa0, pMap->uRegistration); + } + else + { + /* + * C2 range. + */ + Assert(pThis->pMapC1->cb == 0x20); + b -= 0x80; + uint16_t wc = pThis->pMapC1->pauToUni[b]; + if (wc != RTISO2022_UNUSED) + { + int rc = rtIso2022Decoder_ControlCharHook(pThis, wc); + if (RT_SUCCESS(rc)) + { + if (rc == 0) + { + pThis->offString += 1; + *pUniCp = wc; + return VINF_SUCCESS; + } + pThis->offString += rc; + } + else + return rc; + } + else + return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING, + "@%u: C1 b=%#x is marked unused in map #%u.", + pThis->offString, b + 0x80, pThis->pMapC1->uRegistration); + } + } + } + + /* End of string. */ + *pUniCp = RTUNICP_INVALID; + return VERR_END_OF_STRING; +} + +DECLINLINE(int) rtIso2022Decoder_GetNextUniCp(PRTISO2022DECODERSTATE pThis, PRTUNICP pUniCp) +{ + /* + * Deal with single byte GL. + */ + uint32_t const offString = pThis->offString; + if (pThis->offString < pThis->cbString) + { + PCRTISO2022MAP const pMapGL = pThis->pMapGL; + if (pMapGL->cb == 1) + { + uint8_t const b = pThis->pabString[offString] - (uint8_t)0x20; + if (b < pMapGL->cToUni) + { + uint16_t wc = pMapGL->pauToUni[b]; + if (wc != RTISO2022_UNUSED) + { + pThis->offString = offString + 1; + *pUniCp = wc; + return VINF_SUCCESS; + } + } + } + + /* + * Deal with complications in the non-inline function. + */ + return rtIso2022Decoder_GetNextUniCpSlow(pThis, pUniCp); + } + + *pUniCp = RTUNICP_INVALID; + return VERR_END_OF_STRING; +} + + +static int rtIso2022ValidateString(uint32_t uProfile, const char *pch, uint32_t cch, size_t *pcchUtf8, PRTERRINFO pErrInfo) +{ + AssertReturn(uProfile == ASN1_TAG_T61_STRING, VERR_INVALID_PARAMETER); /* just a place holder for now. */ + + RTISO2022DECODERSTATE Decoder; + int rc = rtIso2022Decoder_Init(&Decoder, pch, cch, 102, 106, 107, 102, pErrInfo); + if (RT_SUCCESS(rc)) + { + size_t cchUtf8 = 0; + for (;;) + { + RTUNICP uc; + rc = rtIso2022Decoder_GetNextUniCp(&Decoder, &uc); + if (RT_SUCCESS(rc)) + cchUtf8 += RTStrCpSize(uc); + else + { + if (RT_LIKELY(rc == VERR_END_OF_STRING)) + { + *pcchUtf8 = cchUtf8; + return VINF_SUCCESS; + } + return rc; + } + } + } + return rc; +} + + +static int rtIso2022RecodeAsUtf8(uint32_t uProfile, const char *pchSrc, uint32_t cchSrc, char *pszDst, size_t cbDst) +{ + AssertReturn(uProfile == ASN1_TAG_T61_STRING, VERR_INVALID_PARAMETER); /* just a place holder for now. */ + AssertReturn(cbDst > 0, VERR_INVALID_PARAMETER); + + RTISO2022DECODERSTATE Decoder; + int rc = rtIso2022Decoder_Init(&Decoder, pchSrc, cchSrc, 102, 106, 107, 102, NULL /*pErrInfo*/); + if (RT_SUCCESS(rc)) + { + for (;;) + { + RTUNICP uc; + rc = rtIso2022Decoder_GetNextUniCp(&Decoder, &uc); + if (RT_SUCCESS(rc)) + { + if (uc < 0x80 && cbDst > 1) + { + *pszDst++ = (char)uc; + cbDst--; + } + else + { + size_t cchUniCp = RTStrCpSize(uc); + if (cbDst > cchUniCp) + { + cbDst -= cchUniCp; + pszDst = RTStrPutCp(pszDst, uc); + } + else + { + *pszDst = '\0'; + return VERR_BUFFER_OVERFLOW; + } + } + } + else if (RT_LIKELY(rc == VERR_END_OF_STRING)) + { + *pszDst = '\0'; + return VINF_SUCCESS; + } + else + return rc; + } + } + return rc; +} + + + +/** The unicode mapping of the C1 area of windows codepage 1252. + * The rest of the code page is 1:1 with unicode. */ +static uint16_t g_awcWin1252_C1[0x20] = +{ + 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f, + 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178, +}; + + +static size_t rtWin1252CalcUtf8Length(const char *pch, uint32_t cch) +{ + size_t cchUtf8 = 0; + while (cch-- > 0) + { + uint8_t const b = *pch++; + if (b < 0x80) + cchUtf8 += 1; + else if (b >= 0xa0) + cchUtf8 += 2; + else + { + uint16_t const wc = g_awcWin1252_C1[b - 0x80]; + cchUtf8 += RTStrCpSize(wc); + } + } + return cchUtf8; +} + + +static int rtWin1252RecodeAsUtf8(const char *pchSrc, uint32_t cchSrc, char *pszDst, size_t cbDst) +{ + while (cchSrc-- > 0) + { + uint8_t b = *pchSrc++; + if (b < 0x80) + { + if (cbDst <= 1) + return VERR_BUFFER_OVERFLOW; + *pszDst++ = (char)b; + } + else + { + uint16_t const wc = b >= 0xa0 ? b : g_awcWin1252_C1[b - 0x80]; + size_t cchCp = RTStrCpSize(wc); + if (cbDst <= cchCp) + return VERR_BUFFER_OVERFLOW; + pszDst = RTStrPutCp(pszDst, wc); + } + } + + if (!cbDst) + return VERR_BUFFER_OVERFLOW; + *pszDst = '\0'; + return VINF_SUCCESS; +} + + + +/* + * ASN.1 STRING - Specific Methods. + */ + +/** rtAsn1String_IsTeletexLatin1 results. */ +typedef enum RTASN1TELETEXVARIANT +{ + /** Couldn't find hard evidence of either. */ + RTASN1TELETEXVARIANT_UNDECIDED = 1, + /** Pretty certain that it's real teletex. */ + RTASN1TELETEXVARIANT_TELETEX, + /** Pretty sure it's latin-1 or Windows-1252. */ + RTASN1TELETEXVARIANT_LATIN1, + /** Pretty sure it's Windows-1252. */ + RTASN1TELETEXVARIANT_WIN_1252 +} RTASN1TELETEXVARIANT; + +/** + * Takes a guess as whether TELETEX STRING (T61 STRING) is actually is Latin-1 + * or the real thing. + * + * According to RFC-2459, section 4.1.2.4, various libraries, certificate + * authorities and others have perverted the TeletexString/T61String tag by + * ISO-8859-1 (aka latin-1) strings (more probably these are actually Windows + * CP-1252 rather than latin-1). We'll try detect incompatible latin-1 + * perversions by: + * - The use of GR (0xf0-0xff) chars. + * - The lack of ESC sequences and shifts (LS0,LS1,SS2,SS3) + * + * An ASSUMTION here is that GR is not loaded with anything at the start of a + * teletex string, as per table 3 in section 8.23.5.2 in T-REC-X.590.200811. + * + * @retval @c true if chances are good that it's LATIN-1. + * @retval @c false if changes are very good that it's real teletex. + * @param pch The first char in the string. + * @param cch The string length. + * + * @remarks Useful info on Teletex and ISO/IEC-2022: + * https://www.mail-archive.com/asn1@asn1.org/msg00460.html + * http://en.wikipedia.org/wiki/ISO/IEC_2022 + * http://www.open-std.org/cen/tc304/guide/GCONCEPT.HTM + * http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-035.pdf + */ +static RTASN1TELETEXVARIANT rtAsn1String_IsTeletexLatin1(const char *pch, uint32_t cch) +{ + RTASN1TELETEXVARIANT enmVariant = RTASN1TELETEXVARIANT_UNDECIDED; + while (cch-- > 0) + { + uint8_t const b = *pch; + if (b >= 0x20 && b <= 0x7f) + { + if (g_awcIsoIr102Decode[b - 0x20] == RTISO2022_UNUSED) + enmVariant = RTASN1TELETEXVARIANT_LATIN1; + } + else + { + if ( b == 0x1b /* ESC */ + || b == 0x0e /* LS0 / SI */ + || b == 0x0f /* LS1 / SO */ + || b == 0x19 /* SS2 */ + || b == 0x1d /* SS3 */ ) + return RTASN1TELETEXVARIANT_TELETEX; + + if (b >= 0xa0) + enmVariant = RTASN1TELETEXVARIANT_LATIN1; + else if (b >= 0x80 && b <= 0x9f) + { + /* Any use of C1 characters defined by windows cp-1252 will + lead us to believe it's the windows code rather than the + ISO/IEC standard that is being used. (Not that it makes + much of a difference, because we're gonna treat it as the + windows codepage, anyways.) */ + if ( b != 0x81 + && b != 0x8d + && b != 0x8f + && b != 0x90 + && b != 0x9d) + return RTASN1TELETEXVARIANT_WIN_1252; + } + } + } + return RTASN1TELETEXVARIANT_UNDECIDED; +} + + +/** + * Checks the encoding of an ASN.1 string according to it's tag. + * + * @returns IPRT status code. + * @param pThis The string to santity check. + * @param pErrInfo Where to store extra error info. Optional. + * @param pcchUtf8 Where to return the UTF-8 string length. Optional. + */ +static int rtAsn1String_CheckSanity(PCRTASN1STRING pThis, PRTERRINFO pErrInfo, const char *pszErrorTag, size_t *pcchUtf8) +{ + int rc; + uint32_t cch = pThis->Asn1Core.cb; + size_t cchUtf8 = cch; + const char *pch = pThis->Asn1Core.uData.pch; + uint32_t uTag = RTASN1CORE_GET_TAG(&pThis->Asn1Core); + switch (uTag) + { + case ASN1_TAG_UTF8_STRING: + rc = RTStrValidateEncodingEx(pch, cch, 0); + if (RT_SUCCESS(rc)) + break; + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_UTF8_STRING_ENCODING, "%s: Bad UTF-8 encoding (%Rrc, %.*Rhxs)", + pszErrorTag, rc, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + + case ASN1_TAG_NUMERIC_STRING: + while (cch-- > 0) + { + char ch = *pch++; + if ( !RT_C_IS_DIGIT(ch) + && ch != ' ') + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_NUMERIC_STRING_ENCODING, + "%s: Bad numeric string: ch=%#x (pos %u in %.*Rhxs)", pszErrorTag, ch, + pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + } + break; + + case ASN1_TAG_PRINTABLE_STRING: + while (cch-- > 0) + { + char ch = *pch++; + if ( !RT_C_IS_ALNUM(ch) + && ch != ' ' + && ch != '\'' + && ch != '(' + && ch != ')' + && ch != '+' + && ch != ',' + && ch != '-' + && ch != '.' + && ch != '/' + && ch != ':' + && ch != '=' + && ch != '?' + ) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_PRINTABLE_STRING_ENCODING, + "%s: Bad printable string: ch=%#x (pos %u in %.*Rhxs)", pszErrorTag, ch, + pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + } + break; + + case ASN1_TAG_IA5_STRING: /* ASCII */ + while (cch-- > 0) + { + unsigned char ch = *pch++; + if (ch == 0 || ch >= 0x80) + { + /* Ignore C-style zero terminator as the "Microsoft ECC Product Root Certificate Authority 2018" + for instance, has a policy qualifier string "http://www.microsoft.com/pkiops/Docs/Repository.htm\0" */ + /** @todo should '\0' really be excluded above? */ + if (ch != 0 || cch != 0) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_IA5_STRING_ENCODING, + "%s: Bad IA5 string: ch=%#x (pos %u in %.*Rhxs)", pszErrorTag, ch, + pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + break; + } + } + break; + + case ASN1_TAG_T61_STRING: + switch (rtAsn1String_IsTeletexLatin1(pch, cch)) + { + default: + rc = rtIso2022ValidateString(ASN1_TAG_T61_STRING, pch, cch, &cchUtf8, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + break; + case RTASN1TELETEXVARIANT_UNDECIDED: + case RTASN1TELETEXVARIANT_LATIN1: + case RTASN1TELETEXVARIANT_WIN_1252: + cchUtf8 = rtWin1252CalcUtf8Length(pch, cch); + break; + } + break; + + case ASN1_TAG_VIDEOTEX_STRING: + case ASN1_TAG_GRAPHIC_STRING: + return VERR_ASN1_STRING_TYPE_NOT_IMPLEMENTED; + + case ASN1_TAG_VISIBLE_STRING: + while (cch-- > 0) + { + unsigned char ch = *pch++; + if (ch < 0x20 || ch >= 0x7f) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_VISIBLE_STRING_ENCODING, + "%s: Bad visible string: ch=%#x (pos %u in %.*Rhxs)", pszErrorTag, ch, + pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + } + break; + + case ASN1_TAG_GENERAL_STRING: + return VERR_ASN1_STRING_TYPE_NOT_IMPLEMENTED; + + case ASN1_TAG_UNIVERSAL_STRING: + if (!(cch & 3)) + { + uint8_t const *pb = (uint8_t const *)pch; + cchUtf8 = 0; + while (cch > 0) + { + RTUNICP uc = RT_MAKE_U32_FROM_U8(pb[3], pb[2], pb[1], pb[0]); /* big endian */ + if (!RTUniCpIsValid(uc)) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_UNIVERSAL_STRING_ENCODING, + "%s: Bad universal string: uc=%#x (pos %u in %.*Rhxs)", pszErrorTag, uc, + pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + cchUtf8 += RTUniCpCalcUtf8Len(uc); + + /* next */ + pb += 4; + cch -= 4; + } + break; + } + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_UNIVERSAL_STRING_ENCODING, + "%s: Bad universal string: size not a multiple of 4: cch=%#x (%.*Rhxs)", + pszErrorTag, cch, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + + case ASN1_TAG_BMP_STRING: + if (!(cch & 1)) + { + uint8_t const *pb = (uint8_t const *)pch; + cchUtf8 = 0; + while (cch > 0) + { + RTUNICP uc = RT_MAKE_U32_FROM_U8(pb[1], pb[0], 0, 0); /* big endian */ + if (!RTUniCpIsValid(uc)) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_BMP_STRING_ENCODING, + "%s: Bad BMP string: uc=%#x (pos %u in %.*Rhxs)", pszErrorTag, uc, + pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + cchUtf8 += RTUniCpCalcUtf8Len(uc); + + /* next */ + pb += 2; + cch -= 2; + } + break; + } + return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_BMP_STRING_ENCODING, + "%s: Bad BMP string: odd number of bytes cch=%#x (%.*Rhxs)", + pszErrorTag, cch, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + + default: + AssertMsgFailedReturn(("uTag=%#x\n", uTag), VERR_INTERNAL_ERROR_3); + } + + if (pcchUtf8) + *pcchUtf8 = cchUtf8; + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1String_CompareValues(PCRTASN1STRING pLeft, PCRTASN1STRING pRight) +{ + return RTAsn1String_CompareEx(pLeft, pRight, false /*fTypeToo*/); +} + + +RTDECL(int) RTAsn1String_CompareEx(PCRTASN1STRING pLeft, PCRTASN1STRING pRight, bool fTypeToo) +{ + Assert(pLeft && (!RTAsn1String_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1String_Vtable)); + Assert(pRight && (!RTAsn1String_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1String_Vtable)); + + int iDiff; + if (RTAsn1String_IsPresent(pLeft)) + { + if (RTAsn1String_IsPresent(pRight)) + { + if (!fTypeToo || RTASN1CORE_GET_TAG(&pLeft->Asn1Core) == RTASN1CORE_GET_TAG(&pRight->Asn1Core)) + iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/); + else + iDiff = RTASN1CORE_GET_TAG(&pLeft->Asn1Core) < RTASN1CORE_GET_TAG(&pRight->Asn1Core) ? -1 : 1; + } + else + iDiff = 1; + } + else + iDiff = 0 - RTAsn1String_IsPresent(pRight); + return iDiff; +} + + +RTDECL(int) RTAsn1String_CompareWithString(PCRTASN1STRING pThis, const char *pszString, size_t cchString) +{ + Assert(pThis && (!RTAsn1String_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable)); + AssertPtr(pszString); + + int iDiff; + if (RTAsn1String_IsPresent(pThis)) + { + if (cchString == RTSTR_MAX) + cchString = strlen(pszString); + + /* + * If there is a UTF-8 conversion available already, use it. + */ + if (pThis->pszUtf8) + { + iDiff = strncmp(pThis->pszUtf8, pszString, cchString); + if (!iDiff && pThis->cchUtf8 != cchString) + iDiff = pThis->cchUtf8 < cchString ? -1 : 1; + } + else + { + /* + * Some types are UTF-8 compatible, so try do the compare without + * RTAsn1String_QueryUtf8. + */ + uint32_t cch = pThis->Asn1Core.cb; + const char *pch = pThis->Asn1Core.uData.pch; + switch (RTASN1CORE_GET_TAG(&pThis->Asn1Core)) + { + case ASN1_TAG_UTF8_STRING: + case ASN1_TAG_NUMERIC_STRING: + case ASN1_TAG_IA5_STRING: + case ASN1_TAG_PRINTABLE_STRING: + iDiff = strncmp(pch, pszString, RT_MIN(cch, cchString)); + if (iDiff && cch != cchString) + iDiff = cch < cchString ? - 1 : 1; + break; + + /** @todo Implement comparing ASN1_TAG_BMP_STRING, ASN1_TAG_UNIVERSAL_STRING and + * ASN1_TAG_T61_STRING with UTF-8 strings without conversion. */ + + default: + { + int rc = RTAsn1String_QueryUtf8(pThis, NULL, NULL); + if (RT_SUCCESS(rc)) + { + iDiff = strncmp(pThis->pszUtf8, pszString, cchString); + if (!iDiff && pThis->cchUtf8 != cchString) + iDiff = pThis->cchUtf8 < cchString ? -1 : 1; + } + else + iDiff = -1; + break; + } + } + } + + /* Reduce the strcmp return value. */ + if (iDiff != 0) + iDiff = iDiff < 0 ? -1 : 1; + } + else + iDiff = -1; + return iDiff; +} + + +RTDECL(int) RTAsn1String_QueryUtf8(PCRTASN1STRING pThis, const char **ppsz, size_t *pcch) +{ + Assert(pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable); + + char *psz = (char *)pThis->pszUtf8; + size_t cch = pThis->cchUtf8; + if (!psz) + { + + /* + * Convert the first time around. Start by validating the encoding and + * calculating the length. + */ + int rc = rtAsn1String_CheckSanity(pThis, NULL, NULL, &cch); + if (RT_SUCCESS(rc)) + { + PRTASN1STRING pThisNC = (PRTASN1STRING)pThis; + rc = RTAsn1MemAllocZ(&pThisNC->Allocation, (void **)&psz, cch + 1); + if (RT_SUCCESS(rc)) + { + /* + * Got memory, now do the actual convertion to UTF-8 / copying. + */ + switch (RTASN1CORE_GET_TAG(&pThis->Asn1Core)) + { + case ASN1_TAG_UTF8_STRING: + case ASN1_TAG_NUMERIC_STRING: + case ASN1_TAG_PRINTABLE_STRING: + case ASN1_TAG_IA5_STRING: + case ASN1_TAG_VISIBLE_STRING: + Assert(cch == pThis->Asn1Core.cb); + memcpy(psz, pThis->Asn1Core.uData.pch, cch); + psz[cch] = '\0'; + break; + + case ASN1_TAG_T61_STRING: + switch (rtAsn1String_IsTeletexLatin1(pThis->Asn1Core.uData.pch, pThis->Asn1Core.cb)) + { + default: + rc = rtIso2022RecodeAsUtf8(ASN1_TAG_T61_STRING, pThis->Asn1Core.uData.pch, pThis->Asn1Core.cb, + psz, cch + 1); + break; + case RTASN1TELETEXVARIANT_UNDECIDED: + case RTASN1TELETEXVARIANT_LATIN1: + case RTASN1TELETEXVARIANT_WIN_1252: + rc = rtWin1252RecodeAsUtf8(pThis->Asn1Core.uData.pch, pThis->Asn1Core.cb, psz, cch + 1); + break; + } + AssertReturnStmt(RT_SUCCESS(rc), RTAsn1MemFree(&pThisNC->Allocation, psz), VERR_INTERNAL_ERROR_3); + break; + + /* case ASN1_TAG_VIDEOTEX_STRING: */ + /* case ASN1_TAG_GRAPHIC_STRING: */ + /* case ASN1_TAG_GENERAL_STRING: */ + + case ASN1_TAG_UNIVERSAL_STRING: + { + char *pszDst = psz; + size_t cchSrc = pThis->Asn1Core.cb; + uint8_t const *pbSrc = pThis->Asn1Core.uData.pu8; + while (cchSrc > 0) + { + RTUNICP uc = RT_MAKE_U32_FROM_U8(pbSrc[3], pbSrc[2], pbSrc[1], pbSrc[0]); /* big endian */ + AssertReturnStmt(RTUniCpIsValid(uc), RTAsn1MemFree(&pThisNC->Allocation, psz), VERR_INTERNAL_ERROR_2); + pszDst = RTStrPutCp(pszDst, uc); + + /* next */ + pbSrc += 4; + cchSrc -= 4; + } + Assert((size_t)(pszDst - psz) == cch); + break; + } + + case ASN1_TAG_BMP_STRING: + { + char *pszDst = psz; + size_t cchSrc = pThis->Asn1Core.cb; + uint8_t const *pbSrc = pThis->Asn1Core.uData.pu8; + while (cchSrc > 0) + { + RTUNICP uc = RT_MAKE_U32_FROM_U8(pbSrc[1], pbSrc[0], 0, 0); /* big endian */ + AssertReturnStmt(RTUniCpIsValid(uc), RTAsn1MemFree(&pThisNC->Allocation, psz), VERR_INTERNAL_ERROR_2); + pszDst = RTStrPutCp(pszDst, uc); + + /* next */ + pbSrc += 2; + cchSrc -= 2; + } + Assert((size_t)(pszDst - psz) == cch); + break; + } + + default: + RTAsn1MemFree(&pThisNC->Allocation, psz); + AssertMsgFailedReturn(("uTag=%#x\n", RTASN1CORE_GET_TAG(&pThis->Asn1Core)), VERR_INTERNAL_ERROR_3); + } + + /* + * Successfully produced UTF-8. Save it in the object. + */ + pThisNC->pszUtf8 = psz; + pThisNC->cchUtf8 = (uint32_t)cch; + } + else + return rc; + } + else + return rc; + } + + /* + * Success. + */ + if (ppsz) + *ppsz = psz; + if (pcch) + *pcch = cch; + return VINF_SUCCESS; +} + + + +RTDECL(int) RTAsn1String_QueryUtf8Len(PCRTASN1STRING pThis, size_t *pcch) +{ + Assert(pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable); + + size_t cch = pThis->cchUtf8; + if (!cch && !pThis->pszUtf8) + { + int rc = rtAsn1String_CheckSanity(pThis, NULL, NULL, &cch); + if (RT_FAILURE(rc)) + return rc; + } + + *pcch = cch; + return VINF_SUCCESS; +} + + + + +RTDECL(int) RTAsn1String_InitEx(PRTASN1STRING pThis, uint32_t uTag, void const *pvValue, size_t cbValue, + PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_ZERO(*pThis); + AssertMsgReturn(uTag < RT_ELEMENTS(g_acbStringTags) && g_acbStringTags[uTag] > 0, ("uTag=%#x\n", uTag), + VERR_INVALID_PARAMETER); + + RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator); + RTAsn1Core_InitEx(&pThis->Asn1Core, + uTag, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1String_Vtable, + RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + + if (cbValue > 0) + { + int rc = RTAsn1ContentDup(&pThis->Asn1Core, pvValue, cbValue, pAllocator); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1String_InitWithValue(PRTASN1STRING pThis, const char *pszUtf8Value, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + Assert(RTStrValidateEncoding(pszUtf8Value)); + return RTAsn1String_InitEx(pThis, ASN1_TAG_UTF8_STRING, pszUtf8Value, strlen(pszUtf8Value), pAllocator); +} + + +RTDECL(int) RTAsn1String_RecodeAsUtf8(PRTASN1STRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + /* + * Query the UTF-8 string. Do this even if it's already an + * ASN1_TAG_UTF8_STRING object as it makes sure we've got a valid UTF-8 + * string upon successful return. + */ + int rc = RTAsn1String_QueryUtf8(pThis, NULL, NULL); + if (RT_SUCCESS(rc)) + { + if (RTASN1CORE_GET_TAG(&pThis->Asn1Core) != ASN1_TAG_UTF8_STRING) + { + /* + * Resize the content, copy the UTF-8 bytes in there, and change + * the tag. + */ + rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, pThis->cchUtf8, pAllocator); + if (RT_SUCCESS(rc)) + { + memcpy((void *)pThis->Asn1Core.uData.pv, pThis->pszUtf8, pThis->cchUtf8); + rc = RTAsn1Core_ChangeTag(&pThis->Asn1Core, ASN1_TAG_UTF8_STRING); + } + } + } + return rc; +} + + + +/* + * ASN.1 STRING - Standard Methods. + */ + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1String_Vtable = +{ + "RTAsn1String", + sizeof(RTASN1STRING), + UINT8_MAX, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + 0, + (PFNRTASN1COREVTDTOR)RTAsn1String_Delete, + NULL, + (PFNRTASN1COREVTCLONE)RTAsn1String_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1String_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1String_CheckSanity, + NULL, + NULL +}; + + +RTDECL(int) RTAsn1String_Init(PRTASN1STRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + return RTAsn1String_InitEx(pThis, ASN1_TAG_UTF8_STRING, NULL /*pvValue*/, 0 /*cbValue*/, pAllocator); +} + + +RTDECL(int) RTAsn1String_Clone(PRTASN1STRING pThis, PCRTASN1STRING pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); + RT_ZERO(*pThis); + if (RTAsn1String_IsPresent(pSrc)) + { + AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1String_Vtable, VERR_INTERNAL_ERROR_3); + int rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator); + if (RT_SUCCESS(rc)) + { + /* Don't copy the UTF-8 representation, decode it when queried. */ + RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator); + return VINF_SUCCESS; + } + } + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1String_Delete(PRTASN1STRING pThis) +{ + if ( pThis + && RTAsn1String_IsPresent(pThis)) + { + Assert(pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable); + + if (pThis->Allocation.cbAllocated) + RTAsn1MemFree(&pThis->Allocation, (char *)pThis->pszUtf8); + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1String_Enum(PRTASN1STRING pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); + Assert(pThis && (!RTAsn1String_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable)); + + /* No children to enumerate. */ + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1String_Compare(PCRTASN1STRING pLeft, PCRTASN1STRING pRight) +{ + /* Compare tag and binary value. */ + return RTAsn1String_CompareEx(pLeft, pRight, true /*fTypeToo*/); +} + + +RTDECL(int) RTAsn1String_CheckSanity(PCRTASN1STRING pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + if (RT_UNLIKELY(!RTAsn1String_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (STRING).", pszErrorTag); + return rtAsn1String_CheckSanity(pThis, pErrInfo, pszErrorTag, NULL /*pcchUtf8*/); +} + + +/* + * Generate code for the tag specific methods. + * Note! This is very similar to what we're doing in asn1-ut-time.cpp. + */ +#define RTASN1STRING_IMPL(a_uTag, a_szTag, a_Api) \ + \ + RTDECL(int) RT_CONCAT(a_Api,_Init)(PRTASN1STRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator) \ + { \ + return RTAsn1String_InitEx(pThis, a_uTag, NULL /*pvValue*/, 0 /*cbValue*/, pAllocator); \ + } \ + \ + RTDECL(int) RT_CONCAT(a_Api,_Clone)(PRTASN1STRING pThis, PCRTASN1STRING pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) \ + { \ + AssertReturn(RTASN1CORE_GET_TAG(&pSrc->Asn1Core) == a_uTag || !RTAsn1String_IsPresent(pSrc), \ + VERR_ASN1_STRING_TAG_MISMATCH); \ + return RTAsn1String_Clone(pThis, pSrc, pAllocator); \ + } \ + \ + RTDECL(void) RT_CONCAT(a_Api,_Delete)(PRTASN1STRING pThis) \ + { \ + Assert( !pThis \ + || !RTAsn1String_IsPresent(pThis) \ + || ( pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable \ + && RTASN1CORE_GET_TAG(&pThis->Asn1Core) == a_uTag) ); \ + RTAsn1String_Delete(pThis); \ + } \ + \ + RTDECL(int) RT_CONCAT(a_Api,_Enum)(PRTASN1STRING pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) \ + { \ + RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); \ + Assert( pThis \ + && ( !RTAsn1String_IsPresent(pThis) \ + || ( pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable \ + && RTASN1CORE_GET_TAG(&pThis->Asn1Core) == a_uTag) ) ); \ + /* No children to enumerate. */ \ + return VINF_SUCCESS; \ + } \ + \ + RTDECL(int) RT_CONCAT(a_Api,_Compare)(PCRTASN1STRING pLeft, PCRTASN1STRING pRight) \ + { \ + int iDiff = RTAsn1String_CompareEx(pLeft, pRight, true /*fTypeToo*/); \ + if (!iDiff && RTASN1CORE_GET_TAG(&pLeft->Asn1Core) != a_uTag && RTAsn1String_IsPresent(pLeft)) \ + iDiff = RTASN1CORE_GET_TAG(&pLeft->Asn1Core) < a_uTag ? -1 : 1; \ + return iDiff; \ + } \ + \ + RTDECL(int) RT_CONCAT(a_Api,_CheckSanity)(PCRTASN1STRING pThis, uint32_t fFlags, \ + PRTERRINFO pErrInfo, const char *pszErrorTag) \ + { \ + if (RTASN1CORE_GET_TAG(&pThis->Asn1Core) != a_uTag && RTAsn1String_IsPresent(pThis)) \ + return RTErrInfoSetF(pErrInfo, VERR_ASN1_STRING_TAG_MISMATCH, "%s: uTag=%#x, expected %#x (%s)", \ + pszErrorTag, RTASN1CORE_GET_TAG(&pThis->Asn1Core), a_uTag, a_szTag); \ + return RTAsn1String_CheckSanity(pThis, fFlags, pErrInfo, pszErrorTag); \ + } + +#include "asn1-ut-string-template2.h" + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-string-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-core.h> +#include <iprt/asn1-generator-init.h> +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-time-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-time-decode.cpp new file mode 100644 index 00000000..5df4b73d --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-time-decode.cpp @@ -0,0 +1,415 @@ +/* $Id: asn1-ut-time-decode.cpp $ */ +/** @file + * IPRT - ASN.1, UTC TIME and GENERALIZED TIME Types, Decoding. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/alloca.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/ctype.h> + +#include <iprt/formats/asn1.h> + + +/** + * Common code for UTCTime and GeneralizedTime converters that normalizes the + * converted time and checks that the input values doesn't change. + * + * @returns IPRT status code. + * @param pCursor The cursor to use when reporting an error. + * @param pThis The time to normalize and check. + * @param pszType The type name. + * @param pszErrorTag The error tag. + */ +static int rtAsn1Time_NormalizeTime(PRTASN1CURSOR pCursor, PRTASN1TIME pThis, const char *pszType, const char *pszErrorTag) +{ + int rc; + if ( pThis->Time.u8Month > 0 + && pThis->Time.u8Month <= 12 + && pThis->Time.u8Hour < 24 + && pThis->Time.u8Minute < 60 + && pThis->Time.u8Second <= 60) + { + /* Work around clever rounding error in DER_CFDateToUTCTime() on OS X. This also + supresses any attempt at feeding us leap seconds. If we pass 60 to the + normalization code will move on to the next min/hour/day, which is wrong both + for the OS X issue and for unwanted leap seconds. Leap seconds are not valid + ASN.1 by the by according to the specs available to us. */ + if (pThis->Time.u8Second < 60) + { /* likely */ } + else + pThis->Time.u8Second = 59; + + /* Normalize and move on. */ + RTTIME const TimeCopy = pThis->Time; + if (RTTimeNormalize(&pThis->Time)) + { + if ( TimeCopy.u8MonthDay == pThis->Time.u8MonthDay + && TimeCopy.u8Month == pThis->Time.u8Month + && TimeCopy.i32Year == pThis->Time.i32Year + && TimeCopy.u8Hour == pThis->Time.u8Hour + && TimeCopy.u8Minute == pThis->Time.u8Minute + && TimeCopy.u8Second == pThis->Time.u8Second) + return VINF_SUCCESS; + + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_TIME_NORMALIZE_MISMATCH, + "%s: Normalized result not the same as %s: '%.*s' / %04u-%02u-%02uT%02u:%02u:%02u vs %04u-%02u-%02uT%02u:%02u:%02u", + pszErrorTag, pszType, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch, + TimeCopy.i32Year, TimeCopy.u8Month, TimeCopy.u8MonthDay, + TimeCopy.u8Hour, TimeCopy.u8Minute, TimeCopy.u8Second, + pThis->Time.i32Year, pThis->Time.u8Month, pThis->Time.u8MonthDay, + pThis->Time.u8Hour, pThis->Time.u8Minute, pThis->Time.u8Second); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_TIME_NORMALIZE_ERROR, + "%s: RTTimeNormalize failed on %s: '%.*s'", + pszErrorTag, pszType, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_TIME_BAD_NORMALIZE_INPUT, + "%s: Bad %s values: '%.*s'; mth=%u h=%u min=%u sec=%u", + pszErrorTag, pszType, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch, + pThis->Time.u8Month, pThis->Time.u8Hour, pThis->Time.u8Minute, pThis->Time.u8Second); + return rc; +} + + +/** + * Converts the UTCTime string into an the RTTIME member of RTASN1TIME. + * + * @returns IPRT status code. + * @param pCursor The cursor to use when reporting an error. + * @param pThis The time to parse. + * @param pszErrorTag The error tag. + */ +static int rtAsn1Time_ConvertUTCTime(PRTASN1CURSOR pCursor, PRTASN1TIME pThis, const char *pszErrorTag) +{ + /* + * While the current spec says the seconds field is not optional, this + * restriction was added later on. So, when parsing UTCTime we must deal + * with it being absent. + */ + int rc; + bool fHaveSeconds = pThis->Asn1Core.cb == sizeof("YYMMDDHHMMSSZ") - 1; + if (fHaveSeconds || pThis->Asn1Core.cb == sizeof("YYMMDDHHMMZ") - 1) + { + const char *pachTime = pThis->Asn1Core.uData.pch; + + /* Basic encoding validation. */ + if ( RT_C_IS_DIGIT(pachTime[0]) /* Y */ + && RT_C_IS_DIGIT(pachTime[1]) /* Y */ + && RT_C_IS_DIGIT(pachTime[2]) /* M */ + && RT_C_IS_DIGIT(pachTime[3]) /* M */ + && RT_C_IS_DIGIT(pachTime[4]) /* D */ + && RT_C_IS_DIGIT(pachTime[5]) /* D */ + && RT_C_IS_DIGIT(pachTime[6]) /* H */ + && RT_C_IS_DIGIT(pachTime[7]) /* H */ + && RT_C_IS_DIGIT(pachTime[8]) /* M */ + && RT_C_IS_DIGIT(pachTime[9]) /* M */ + && ( !fHaveSeconds + || ( RT_C_IS_DIGIT(pachTime[10]) /* S */ + && RT_C_IS_DIGIT(pachTime[11]) /* S */ ) ) + && pachTime[fHaveSeconds ? 12 : 10] == 'Z' + ) + { + /* Basic conversion. */ + pThis->Time.i32Year = (pachTime[0] - '0') * 10 + (pachTime[1] - '0'); + pThis->Time.i32Year += pThis->Time.i32Year < 50 ? 2000 : 1900; + pThis->Time.u8Month = (pachTime[2] - '0') * 10 + (pachTime[3] - '0'); + pThis->Time.u8WeekDay = 0; + pThis->Time.u16YearDay = 0; + pThis->Time.u8MonthDay = (pachTime[4] - '0') * 10 + (pachTime[5] - '0'); + pThis->Time.u8Hour = (pachTime[6] - '0') * 10 + (pachTime[7] - '0'); + pThis->Time.u8Minute = (pachTime[8] - '0') * 10 + (pachTime[9] - '0'); + if (fHaveSeconds) + pThis->Time.u8Second = (pachTime[10] - '0') * 10 + (pachTime[11] - '0'); + else + pThis->Time.u8Second = 0; + pThis->Time.u32Nanosecond = 0; + pThis->Time.fFlags = RTTIME_FLAGS_TYPE_UTC; + pThis->Time.offUTC = 0; + + /* Check the convered data and normalize the time structure. */ + rc = rtAsn1Time_NormalizeTime(pCursor, pThis, "UTCTime", pszErrorTag); + if (RT_SUCCESS(rc)) + return rc; + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_UTC_TIME_ENCODING, "%s: Bad UTCTime encoding: '%.*s'", + pszErrorTag, pThis->Asn1Core.cb, pachTime); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_UTC_TIME_ENCODING, "%s: Bad UTCTime length: %#x", + pszErrorTag, pThis->Asn1Core.cb); + RT_ZERO(*pThis); + return rc; +} + + +/** + * Converts the fraction part of a generalized time into nanoseconds. + * + * @returns IPRT status code. + * @param pCursor The cursor to use when reporting an error. + * @param pchFraction Pointer to the start of the fraction (dot). + * @param cchFraction The length of the fraction. + * @param pThis The time object we're working on, + * Time.u32Nanoseconds will be update. + * @param pszErrorTag The error tag. + */ +static int rtAsn1Time_ConvertGeneralizedTimeFraction(PRTASN1CURSOR pCursor, const char *pchFraction, uint32_t cchFraction, + PRTASN1TIME pThis, const char *pszErrorTag) +{ + pThis->Time.u32Nanosecond = 0; + + /* + * Check the dot. + */ + if (*pchFraction != '.') + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING, + "%s: Expected GeneralizedTime fraction dot, found: '%c' ('%.*s')", + pszErrorTag, *pchFraction, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + pchFraction++; + cchFraction--; + if (!cchFraction) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING, + "%s: No digit following GeneralizedTime fraction dot: '%.*s'", + pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core); + + /* + * Do the conversion. + */ + char chLastDigit; + uint32_t uMult = 100000000; + do + { + char chDigit = chLastDigit = *pchFraction; + if (!RT_C_IS_DIGIT(chDigit)) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING, + "%s: Bad GeneralizedTime fraction digit: '%.*s'", + pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + pThis->Time.u32Nanosecond += uMult * (uint32_t)(chDigit - '0'); + + /* Advance */ + cchFraction--; + pchFraction++; + uMult /= 10; + } while (cchFraction > 0 && uMult > 0); + + /* + * Lazy bird: For now, we don't permit higher resolution than we can + * internally represent. Deal with this if it ever becomes an issue. + */ + if (cchFraction > 0) + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING, + "%s: Bad GeneralizedTime fraction too long: '%.*s'", + pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + if (chLastDigit == '0') + return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING, + "%s: Trailing zeros not allowed for GeneralizedTime: '%.*s'", + pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch); + return VINF_SUCCESS; +} + + +/** + * Converts the GeneralizedTime string into an the RTTIME member of RTASN1TIME. + * + * @returns IPRT status code. + * @param pCursor The cursor to use when reporting an error. + * @param pThis The time to parse. + * @param pszErrorTag The error tag. + */ +static int rtAsn1Time_ConvertGeneralizedTime(PRTASN1CURSOR pCursor, PRTASN1TIME pThis, const char *pszErrorTag) +{ + int rc; + if (pThis->Asn1Core.cb >= sizeof("YYYYMMDDHHMMSSZ") - 1) + { + const char *pachTime = pThis->Asn1Core.uData.pch; + + /* Basic encoding validation. */ + if ( RT_C_IS_DIGIT(pachTime[0]) /* Y */ + && RT_C_IS_DIGIT(pachTime[1]) /* Y */ + && RT_C_IS_DIGIT(pachTime[2]) /* Y */ + && RT_C_IS_DIGIT(pachTime[3]) /* Y */ + && RT_C_IS_DIGIT(pachTime[4]) /* M */ + && RT_C_IS_DIGIT(pachTime[5]) /* M */ + && RT_C_IS_DIGIT(pachTime[6]) /* D */ + && RT_C_IS_DIGIT(pachTime[7]) /* D */ + && RT_C_IS_DIGIT(pachTime[8]) /* H */ + && RT_C_IS_DIGIT(pachTime[9]) /* H */ + && RT_C_IS_DIGIT(pachTime[10]) /* M */ + && RT_C_IS_DIGIT(pachTime[11]) /* M */ + && RT_C_IS_DIGIT(pachTime[12]) /* S */ /** @todo was this once optional? */ + && RT_C_IS_DIGIT(pachTime[13]) /* S */ + && pachTime[pThis->Asn1Core.cb - 1] == 'Z' + ) + { + /* Basic conversion. */ + pThis->Time.i32Year = 1000 * (pachTime[0] - '0') + + 100 * (pachTime[1] - '0') + + 10 * (pachTime[2] - '0') + + (pachTime[3] - '0'); + pThis->Time.u8Month = (pachTime[4] - '0') * 10 + (pachTime[5] - '0'); + pThis->Time.u8WeekDay = 0; + pThis->Time.u16YearDay = 0; + pThis->Time.u8MonthDay = (pachTime[6] - '0') * 10 + (pachTime[7] - '0'); + pThis->Time.u8Hour = (pachTime[8] - '0') * 10 + (pachTime[9] - '0'); + pThis->Time.u8Minute = (pachTime[10] - '0') * 10 + (pachTime[11] - '0'); + pThis->Time.u8Second = (pachTime[12] - '0') * 10 + (pachTime[13] - '0'); + pThis->Time.u32Nanosecond = 0; + pThis->Time.fFlags = RTTIME_FLAGS_TYPE_UTC; + pThis->Time.offUTC = 0; + + /* Optional fraction part. */ + rc = VINF_SUCCESS; + uint32_t cchLeft = pThis->Asn1Core.cb - 14 - 1; + if (cchLeft > 0) + rc = rtAsn1Time_ConvertGeneralizedTimeFraction(pCursor, pachTime + 14, cchLeft, pThis, pszErrorTag); + + /* Check the convered data and normalize the time structure. */ + if (RT_SUCCESS(rc)) + { + rc = rtAsn1Time_NormalizeTime(pCursor, pThis, "GeneralizedTime", pszErrorTag); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + } + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING, + "%s: Bad GeneralizedTime encoding: '%.*s'", + pszErrorTag, pThis->Asn1Core.cb, pachTime); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING, + "%s: Bad GeneralizedTime length: %#x", + pszErrorTag, pThis->Asn1Core.cb); + RT_ZERO(*pThis); + return rc; +} + + +RTDECL(int) RTAsn1Time_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1TIME pThis, const char *pszErrorTag) +{ + Assert(!(fFlags & RTASN1CURSOR_GET_F_IMPLICIT)); RT_NOREF_PV(fFlags); + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + if (pThis->Asn1Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE) ) + { + if (pThis->Asn1Core.uTag == ASN1_TAG_UTC_TIME) + { + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1Time_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + return rtAsn1Time_ConvertUTCTime(pCursor, pThis, pszErrorTag); + } + + if (pThis->Asn1Core.uTag == ASN1_TAG_GENERALIZED_TIME) + { + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1Time_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + return rtAsn1Time_ConvertGeneralizedTime(pCursor, pThis, pszErrorTag); + } + + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TAG_MISMATCH, "%s: Not UTCTime nor GeneralizedTime: uTag=%#x", + pszErrorTag, pThis->Asn1Core.uTag); + } + else + rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TAG_FLAG_CLASS_MISMATCH, + "%s: Not UTCTime nor GeneralizedTime: fClass=%#x / uTag=%#x", + pszErrorTag, pThis->Asn1Core.fClass, pThis->Asn1Core.uTag); + } + RT_ZERO(*pThis); + return rc; +} + + +RTDECL(int) RTAsn1UtcTime_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1TIME pThis, const char *pszErrorTag) +{ + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_UTC_TIME, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + fFlags, pszErrorTag, "UTC TIME"); + if (RT_SUCCESS(rc)) + { + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1Time_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + return rtAsn1Time_ConvertUTCTime(pCursor, pThis, pszErrorTag); + } + } + RT_ZERO(*pThis); + return rc; +} + + +RTDECL(int) RTAsn1GeneralizedTime_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1TIME pThis, const char *pszErrorTag) +{ + int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_GENERALIZED_TIME, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + fFlags, pszErrorTag, "GENERALIZED TIME"); + if (RT_SUCCESS(rc)) + { + RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb); + pThis->Asn1Core.pOps = &g_RTAsn1Time_Vtable; + pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT; + return rtAsn1Time_ConvertGeneralizedTime(pCursor, pThis, pszErrorTag); + } + } + RT_ZERO(*pThis); + return rc; +} + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-time-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-time-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-time-template.h new file mode 100644 index 00000000..06ba84aa --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-time-template.h @@ -0,0 +1,55 @@ +/* $Id: asn1-ut-time-template.h $ */ +/** @file + * IPRT - ASN.1, UTC TIME and GENERALIZED TIME Types, Collection Type Template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +#define RTASN1TMPL_TYPE RTASN1SEQOFTIMES +#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfTimes +#define RTASN1TMPL_INT_NAME rtAsn1SeqOfTimes +RTASN1TMPL_SEQ_OF(RTASN1TIME, RTAsn1Time); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +#define RTASN1TMPL_TYPE RTASN1SETOFTIMES +#define RTASN1TMPL_EXT_NAME RTAsn1SetOfTimes +#define RTASN1TMPL_INT_NAME rtAsn1SetOfTimes +RTASN1TMPL_SET_OF(RTASN1TIME, RTAsn1Time); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-time-template2.h b/src/VBox/Runtime/common/asn1/asn1-ut-time-template2.h new file mode 100644 index 00000000..1c341bdc --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-time-template2.h @@ -0,0 +1,40 @@ +/* $Id: asn1-ut-time-template2.h $ */ +/** @file + * IPRT - ASN.1, UTC TIME and GENERALIZED TIME Types, Template for type specific wrappers. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +RTASN1TIME_IMPL(ASN1_TAG_UTC_TIME, "UTC TIME", RTAsn1UtcTime) +RTASN1TIME_IMPL(ASN1_TAG_GENERALIZED_TIME, "GENERALIZED TIME", RTAsn1GeneralizedTime) + diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-time.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-time.cpp new file mode 100644 index 00000000..d7176e91 --- /dev/null +++ b/src/VBox/Runtime/common/asn1/asn1-ut-time.cpp @@ -0,0 +1,359 @@ +/* $Id: asn1-ut-time.cpp $ */ +/** @file + * IPRT - ASN.1, UTC TIME and GENERALIZED TIME Types. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/asn1.h> + +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include <iprt/formats/asn1.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The UTC TIME encoding of the IPRT epoch time. */ +static const char g_szEpochUtc[] = "700101000000Z"; +/** The GENERALIZED TIME encoding of the IPRT epoch time. */ +static const char g_szEpochGeneralized[] = "19700101000000Z"; + + +/* + * ASN.1 TIME - Special Methods. + */ + +RTDECL(int) RTAsn1Time_InitEx(PRTASN1TIME pThis, uint32_t uTag, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + RT_NOREF_PV(pAllocator); + AssertReturn(uTag == ASN1_TAG_UTC_TIME || uTag == ASN1_TAG_GENERALIZED_TIME, VERR_INVALID_PARAMETER); + RTAsn1Core_InitEx(&pThis->Asn1Core, + uTag, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + &g_RTAsn1Time_Vtable, + RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT); + if (uTag == ASN1_TAG_UTC_TIME) + { + pThis->Asn1Core.cb = sizeof(g_szEpochUtc) - 1; + pThis->Asn1Core.uData.pv = &g_szEpochUtc[0]; + } + else + { + pThis->Asn1Core.cb = sizeof(g_szEpochGeneralized) - 1; + pThis->Asn1Core.uData.pv = &g_szEpochGeneralized[0]; + } + + RTTIMESPEC EpochTimeSpec; + RTTimeExplode(&pThis->Time, RTTimeSpecSetSeconds(&EpochTimeSpec, 0)); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Time_InitWithTime(PRTASN1TIME pThis, uint32_t uTag, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIME pTime) +{ + int rc = RTAsn1Time_InitEx(pThis, uTag, pAllocator); /* this doens't leave any state needing deletion */ + if (RT_SUCCESS(rc) && pTime) + rc = RTAsn1Time_SetTime(pThis, pAllocator, pTime); + return rc; +} + + +RTDECL(int) RTAsn1Time_SetTime(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIME pTime) +{ + /* + * Validate input. + */ + AssertReturn(RTAsn1Time_IsPresent(pThis), VERR_INVALID_STATE); /* Use RTAsn1Time_InitWithTime. */ + + RTTIMESPEC TmpTimeSpec; + AssertReturn(RTTimeImplode(&TmpTimeSpec, pTime), VERR_INVALID_PARAMETER); + RTTIME NormalizedTime; + RTTimeExplode(&NormalizedTime, &TmpTimeSpec); + + uint32_t const uTag = RTASN1CORE_GET_TAG(&pThis->Asn1Core); + if (uTag == ASN1_TAG_UTC_TIME) + { + AssertReturn(NormalizedTime.i32Year >= 1950, VERR_INVALID_PARAMETER); + AssertReturn(NormalizedTime.i32Year < 2050, VERR_INVALID_PARAMETER); + } + else + { + AssertReturn(uTag == ASN1_TAG_GENERAL_STRING, VERR_INVALID_STATE); + AssertReturn(NormalizedTime.i32Year >= 0, VERR_INVALID_PARAMETER); + AssertReturn(NormalizedTime.i32Year < 9999, VERR_INVALID_PARAMETER); + } + + /* + * Format the string to a temporary buffer, since the ASN.1 content isn't + * zero terminated and we cannot use RTStrPrintf directly on it. + */ + char szTmp[64]; + uint32_t cchTime; + if (uTag == ASN1_TAG_UTC_TIME) + cchTime = (uint32_t)RTStrPrintf(szTmp, sizeof(szTmp), "%02u%02u%02u%02u%02u%02uZ", + NormalizedTime.i32Year % 100, + NormalizedTime.u8Month, + NormalizedTime.u8MonthDay, + NormalizedTime.u8Hour, + NormalizedTime.u8Minute, + NormalizedTime.u8Second); + else + cchTime = (uint32_t)RTStrPrintf(szTmp, sizeof(szTmp), "%04u%02u%02u%02u%02u%02uZ", + NormalizedTime.i32Year, + NormalizedTime.u8Month, + NormalizedTime.u8MonthDay, + NormalizedTime.u8Hour, + NormalizedTime.u8Minute, + NormalizedTime.u8Second); + AssertReturn(cchTime == (uTag == ASN1_TAG_UTC_TIME ? sizeof(g_szEpochUtc) - 1 : sizeof(g_szEpochGeneralized) - 1), + VERR_INTERNAL_ERROR_3); + + /* + * (Re-)Allocate content buffer, copy over the formatted timestamp and + * set the exploded time member to the new time. + */ + int rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, cchTime, pAllocator); + if (RT_SUCCESS(rc)) + { + memcpy((void *)pThis->Asn1Core.uData.pv, szTmp, cchTime); + pThis->Time = NormalizedTime; + } + return rc; +} + + +RTDECL(int) RTAsn1Time_SetTimeSpec(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIMESPEC pTimeSpec) +{ + RTTIME Time; + return RTAsn1Time_SetTime(pThis, pAllocator, RTTimeExplode(&Time, pTimeSpec)); +} + + +RTDECL(int) RTAsn1Time_CompareWithTimeSpec(PCRTASN1TIME pLeft, PCRTTIMESPEC pTsRight) +{ + int iDiff = RTAsn1Time_IsPresent(pLeft) ? 0 : -1; + if (!iDiff) + { + RTTIME RightTime; + iDiff = RTTimeCompare(&pLeft->Time, RTTimeExplode(&RightTime, pTsRight)); + } + + return iDiff; +} + + +/* + * ASN.1 TIME - Standard Methods. + */ + +RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Time_Vtable = +{ + "RTAsn1Time", + sizeof(RTASN1TIME), + UINT8_MAX, + ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, + 0, + (PFNRTASN1COREVTDTOR)RTAsn1Time_Delete, + NULL, + (PFNRTASN1COREVTCLONE)RTAsn1Time_Clone, + (PFNRTASN1COREVTCOMPARE)RTAsn1Time_Compare, + (PFNRTASN1COREVTCHECKSANITY)RTAsn1Time_CheckSanity, + NULL, + NULL +}; + + +RTDECL(int) RTAsn1Time_Init(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + /* Using UTC TIME since epoch would be encoded using UTC TIME following + X.509 Validity / Whatever time tag guidelines. */ + return RTAsn1Time_InitEx(pThis, ASN1_TAG_UTC_TIME, pAllocator); +} + + +RTDECL(int) RTAsn1Time_Clone(PRTASN1TIME pThis, PCRTASN1TIME pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); + RT_ZERO(*pThis); + if (RTAsn1Time_IsPresent(pSrc)) + { + AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1Time_Vtable, VERR_INTERNAL_ERROR_3); + + int rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator); + if (RT_SUCCESS(rc)) + { + pThis->Time = pSrc->Time; + return VINF_SUCCESS; + } + return rc; + } + return VINF_SUCCESS; +} + + +RTDECL(void) RTAsn1Time_Delete(PRTASN1TIME pThis) +{ + if ( pThis + && RTAsn1Time_IsPresent(pThis)) + { + Assert(pThis->Asn1Core.pOps == &g_RTAsn1Time_Vtable); + + RTAsn1ContentFree(&pThis->Asn1Core); + RT_ZERO(*pThis); + } +} + + +RTDECL(int) RTAsn1Time_Enum(PRTASN1TIME pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) +{ + RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); + Assert(pThis && (!RTAsn1Time_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1Time_Vtable)); + + /* No children to enumerate. */ + return VINF_SUCCESS; +} + + +RTDECL(int) RTAsn1Time_Compare(PCRTASN1TIME pLeft, PCRTASN1TIME pRight) +{ + Assert(pLeft && (!RTAsn1Time_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1Time_Vtable)); + Assert(pRight && (!RTAsn1Time_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1Time_Vtable)); + + int iDiff; + if (RTAsn1Time_IsPresent(pLeft)) + { + if (RTAsn1Time_IsPresent(pRight)) + iDiff = RTTimeCompare(&pLeft->Time, &pRight->Time); + else + iDiff = -1; + } + else + iDiff = 0 - (int)RTAsn1Time_IsPresent(pRight); + return iDiff; +} + + +RTDECL(int) RTAsn1Time_CheckSanity(PCRTASN1TIME pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + if (RT_UNLIKELY(!RTAsn1Time_IsPresent(pThis))) + return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (TIME).", pszErrorTag); + return VINF_SUCCESS; +} + + +/* + * Generate code for the tag specific methods. + * Note! This is very similar to what we're doing in asn1-ut-string.cpp. + */ +#define RTASN1TIME_IMPL(a_uTag, a_szTag, a_Api) \ + \ + RTDECL(int) RT_CONCAT(a_Api,_Init)(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator) \ + { \ + return RTAsn1Time_InitEx(pThis, a_uTag, pAllocator); \ + } \ + \ + RTDECL(int) RT_CONCAT(a_Api,_Clone)(PRTASN1TIME pThis, PCRTASN1TIME pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) \ + { \ + AssertReturn(RTASN1CORE_GET_TAG(&pSrc->Asn1Core) == a_uTag || !RTAsn1Time_IsPresent(pSrc), \ + VERR_ASN1_TIME_TAG_MISMATCH); \ + return RTAsn1Time_Clone(pThis, pSrc, pAllocator); \ + } \ + \ + RTDECL(void) RT_CONCAT(a_Api,_Delete)(PRTASN1TIME pThis) \ + { \ + Assert( !pThis \ + || !RTAsn1Time_IsPresent(pThis) \ + || ( pThis->Asn1Core.pOps == &g_RTAsn1Time_Vtable \ + && RTASN1CORE_GET_TAG(&pThis->Asn1Core) == a_uTag) ); \ + RTAsn1Time_Delete(pThis); \ + } \ + \ + RTDECL(int) RT_CONCAT(a_Api,_Enum)(PRTASN1TIME pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) \ + { \ + RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); \ + Assert( pThis \ + && ( !RTAsn1Time_IsPresent(pThis) \ + || ( pThis->Asn1Core.pOps == &g_RTAsn1Time_Vtable \ + && RTASN1CORE_GET_TAG(&pThis->Asn1Core) == a_uTag) ) ); \ + /* No children to enumerate. */ \ + return VINF_SUCCESS; \ + } \ + \ + RTDECL(int) RT_CONCAT(a_Api,_Compare)(PCRTASN1TIME pLeft, PCRTASN1TIME pRight) \ + { \ + int iDiff = RTAsn1Time_Compare(pLeft, pRight); \ + if (!iDiff && RTAsn1Time_IsPresent(pLeft)) \ + { \ + if (RTASN1CORE_GET_TAG(&pLeft->Asn1Core) == RTASN1CORE_GET_TAG(&pRight->Asn1Core)) \ + { \ + if (RTASN1CORE_GET_TAG(&pLeft->Asn1Core) != a_uTag) \ + iDiff = RTASN1CORE_GET_TAG(&pLeft->Asn1Core) < a_uTag ? -1 : 1; \ + } \ + else \ + iDiff = RTASN1CORE_GET_TAG(&pLeft->Asn1Core) < RTASN1CORE_GET_TAG(&pRight->Asn1Core) ? -1 : 1; \ + } \ + return iDiff; \ + } \ + \ + RTDECL(int) RT_CONCAT(a_Api,_CheckSanity)(PCRTASN1TIME pThis, uint32_t fFlags, \ + PRTERRINFO pErrInfo, const char *pszErrorTag) \ + { \ + if (RTASN1CORE_GET_TAG(&pThis->Asn1Core) != a_uTag && RTAsn1Time_IsPresent(pThis)) \ + return RTErrInfoSetF(pErrInfo, VERR_ASN1_TIME_TAG_MISMATCH, "%s: uTag=%#x, expected %#x (%s)", \ + pszErrorTag, RTASN1CORE_GET_TAG(&pThis->Asn1Core), a_uTag, a_szTag); \ + return RTAsn1Time_CheckSanity(pThis, fFlags, pErrInfo, pszErrorTag); \ + } + +#include "asn1-ut-time-template2.h" + + +/* + * Generate code for the associated collection types. + */ +#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-time-template.h" +#include <iprt/asn1-generator-internal-header.h> +#include <iprt/asn1-generator-core.h> +#include <iprt/asn1-generator-init.h> +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/asn1/oiddb.cfg b/src/VBox/Runtime/common/asn1/oiddb.cfg new file mode 100644 index 00000000..000bc47d --- /dev/null +++ b/src/VBox/Runtime/common/asn1/oiddb.cfg @@ -0,0 +1,351 @@ +# $Id: oiddb.cfg $ +## @file +# OID names. +# +# This is processed by oid2c.cpp and turned into a database which +# the ASN.1 dumper code is using. +# +# Two formats here: +# 1. Simple: +# <ODI> = <name> +# 2. The dumpasn1.cfg one: +# ODI = <ODI> +# <other stuff> = <ignored> +# Description = <Description> +# +# The file doesn't need to be sorted, but we keep it sorted to make it +# simpler to insert new OIDs and spot bugs. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox 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. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +1.2.840 = usa +1.2.840.10045 = ansi-x962 +1.2.840.10045.2 = ansi-x962-keyType +1.2.840.10045.2.1 = ansi-x962-ecPublicKey +1.2.840.10045.4 = ansi-x962-signatures +1.2.840.10045.4.1 = ecdsa-with-sha1 +1.2.840.10045.4.2 = ecdsa-with-recommended +1.2.840.10045.4.3 = ecdsa-with-sha2 +1.2.840.10045.4.3.1 = ecdsa-with-sha224 +1.2.840.10045.4.3.2 = ecdsa-with-sha256 +1.2.840.10045.4.3.3 = ecdsa-with-sha384 +1.2.840.10045.4.3.4 = ecdsa-with-sha512 +1.2.840.113549 = rsadsi +1.2.840.113549.1 = pkcs +1.2.840.113549.1.1 = pkcs1 +1.2.840.113549.1.1.1 = pkcs1-RsaEncryption +1.2.840.113549.1.1.2 = pkcs1-Md2WithRsaEncryption +1.2.840.113549.1.1.3 = pkcs1-Md4WithRsaEncryption +1.2.840.113549.1.1.4 = pkcs1-Md5WithRsaEncryption +1.2.840.113549.1.1.5 = pkcs1-Sha1WithRsaEncryption +1.2.840.113549.1.1.10 = pkcs1-RsaPss +1.2.840.113549.1.1.11 = pkcs1-Sha256WithRsaEncryption +1.2.840.113549.1.1.12 = pkcs1-Sha384WithRsaEncryption +1.2.840.113549.1.1.13 = pkcs1-Sha512WithRsaEncryption +1.2.840.113549.1.1.14 = pkcs1-Sha224WithRsaEncryption +1.2.840.113549.1.1.15 = pkcs1-Sha512-224WithRsaEncryption +1.2.840.113549.1.1.16 = pkcs1-Sha512-256WithRsaEncryption +1.2.840.113549.1.7 = pkcs7 +1.2.840.113549.1.7.1 = pkcs7-data +1.2.840.113549.1.7.2 = pkcs7-signedData +1.2.840.113549.1.7.3 = pkcs7-envelopedData +1.2.840.113549.1.7.4 = pkcs7-signedAndEnvelopedData +1.2.840.113549.1.7.5 = pkcs7-digestedData +1.2.840.113549.1.7.6 = pkcs7-encryptedData +1.2.840.113549.1.9 = pkcs9 +1.2.840.113549.1.9.1 = pkcs9-EMailAddress +1.2.840.113549.1.9.2 = pkcs9-UntrustedName +1.2.840.113549.1.9.3 = pkcs9-ContentType +1.2.840.113549.1.9.4 = pkcs9-MessageDigest +1.2.840.113549.1.9.5 = pkcs9-SigningTime +1.2.840.113549.1.9.6 = pkcs9-CounterSignature +1.2.840.113549.1.9.7 = pkcs9-challengePassword +1.2.840.113549.1.9.8 = pkcs9-UnstructuredAddress +1.2.840.113549.1.9.9 = pkcs9-ExtendedCertificateAttributes +1.2.840.113549.1.9.13 = pkcs9-SigningDescription +1.2.840.113549.1.9.14 = pkcs9-ExtensionRequest +1.2.840.113549.1.9.15 = pkcs9-SMimeCapabilities +1.2.840.113549.1.9.16 = pkcs9-SMime +1.2.840.113549.1.9.16.1 = pkcs9-SMime-ct +1.2.840.113549.1.9.16.1.2 = pkcs9-ct-AuthData +1.2.840.113549.1.9.16.1.4 = pkcs9-ct-TimestampTokenInfo +1.2.840.113549.1.9.16.1.9 = pkcs9-ct-CompressedData +1.2.840.113549.1.9.16.1.23 = pkcs9-ct-AuthEnvelopedData +1.2.840.113549.1.9.16.1.31 = pkcs9-ct-TimestampedData +1.2.840.113549.1.9.16.2 = pkcs9-SMime-id-aa +1.2.840.113549.1.9.16.2.12 = pkcs9-id-aa-SigningCertificate +1.2.840.113549.1.9.16.2.14 = pkcs9-id-aa-Attributes +1.2.840.113549.1.9.16.2.47 = pkcs9-id-aa-signingCertificateV2 +1.2.840.113549.1.9.25 = pkcs9-SMime-at +1.2.840.113549.1.9.25.1 = pkcs9-at-Pkcs15Token +1.2.840.113549.1.9.25.2 = pkcs9-at-EncryptedPrivateKeyInfo +1.2.840.113549.1.9.25.3 = pkcs9-at-RandomNonce +1.2.840.113549.1.9.25.4 = pkcs9-at-SequenceNumber +1.2.840.113549.1.9.25.5 = pkcs9-at-Pkcs7PDU +1.2.840.113635.100.6.1.13 = apple-cs-ext-DevId-Application +1.2.840.113635.100.6.1.14 = apple-cs-ext-DevId-Installer +1.2.840.113635.100.6.1.18 = apple-cs-ext-DevId-KernelExt +1.2.840.113635.100.5.1 = apple-cert-policy +1.2.840.113635.100.4.13 = apple-eku-packageSign +#1.2.840.113635.100.9.1 = apple-??? +1.3.6 = dod +1.3.6.1 = dod-Internet +1.3.6.1.4.1.311 = microsoft +1.3.6.1.4.1.311.2 = ms-authenticode +1.3.6.1.4.1.311.2.1 = ms-spc +1.3.6.1.4.1.311.2.1.1 = Ms-??-2.1 +1.3.6.1.4.1.311.2.1.4 = Ms-SpcIndirectDataContext +1.3.6.1.4.1.311.2.1.10 = Ms-SpcAgencyInfo +1.3.6.1.4.1.311.2.1.11 = Ms-SpcStatementType +1.3.6.1.4.1.311.2.1.12 = Ms-SpcOpusInfo +1.3.6.1.4.1.311.2.1.14 = Ms-CertReqExtensions +1.3.6.1.4.1.311.2.1.15 = Ms-SpcPeImageData +1.3.6.1.4.1.311.2.1.18 = Ms-SpcRawFileData +1.3.6.1.4.1.311.2.1.19 = Ms-SpcStructuredStorageData +1.3.6.1.4.1.311.2.1.20 = Ms-SpcJavaClassDataType1 +1.3.6.1.4.1.311.2.1.21 = Ms-SpcIndividualCodeSigning +1.3.6.1.4.1.311.2.1.22 = Ms-SpcCommericalSigning +1.3.6.1.4.1.311.2.1.25 = Ms-SpcLinkType2-Aka-CabData +1.3.6.1.4.1.311.2.1.26 = Ms-SpcMinimalCriterialInfo +1.3.6.1.4.1.311.2.1.27 = Ms-SpcFinacialCriterialInfo +1.3.6.1.4.1.311.2.1.28 = Ms-SpcLinkType3 +1.3.6.1.4.1.311.2.1.29 = Ms-SpcHashInfo +1.3.6.1.4.1.311.2.1.30 = Ms-SpcSipInfo +1.3.6.1.4.1.311.2.2 = Ms-Ctl-CAs +1.3.6.1.4.1.311.2.2.1 = Ms-TrustedCodeSigningCAList +1.3.6.1.4.1.311.2.2.2 = Ms-TrustedClientAuthCAList +1.3.6.1.4.1.311.2.2.3 = Ms-TrustedServerAuthCAList +1.3.6.1.4.1.311.2.3.1 = Ms-SpcPeImagePageHashesV1 +1.3.6.1.4.1.311.2.3.2 = Ms-SpcPeImagePageHashesV2 +1.3.6.1.4.1.311.2.4 = Ms-??-.2.4 +1.3.6.1.4.1.311.2.4.1 = Ms-SpcNestedSignature +1.3.6.1.4.1.311.3 = ms-timestamping +1.3.6.1.4.1.311.3.2.1 = Ms-SpcTimeStampRequest +1.3.6.1.4.1.311.3.3.1 = Ms-CounterSign +1.3.6.1.4.1.311.3.3.2 = Ms-?-3.2 +1.3.6.1.4.1.311.10.1 = Ms-CertTrustList +1.3.6.1.4.1.311.10.1.1 = Ms-SortedCertTrustList +1.3.6.1.4.1.311.10.2 = Ms-NextUpdateLocation +1.3.6.1.4.1.311.10.3 = Ms-EKU +1.3.6.1.4.1.311.10.3.1 = Ms-kp-CertTrustListSigning +1.3.6.1.4.1.311.10.3.2 = Ms-kp-TimeStampSigning +1.3.6.1.4.1.311.10.3.3 = Ms-ServerGatedCrypto +1.3.6.1.4.1.311.10.3.3.1 = Ms-Serialized +1.3.6.1.4.1.311.10.3.4 = Ms-EncryptedFileSystem +1.3.6.1.4.1.311.10.3.4.1 = Ms-EncryptedFileSystem-Recovery +1.3.6.1.4.1.311.10.3.5 = Ms-WhqlCrypto +1.3.6.1.4.1.311.10.3.6 = Ms-Nt5Crypto +1.3.6.1.4.1.311.10.3.7 = Ms-OemWhqlCrypto +1.3.6.1.4.1.311.10.3.8 = Ms-EmbeddedNtCrypto +1.3.6.1.4.1.311.10.3.9 = Ms-RootListSigner +1.3.6.1.4.1.311.10.3.10 = Ms-QualifiedSubordination +1.3.6.1.4.1.311.10.3.11 = Ms-KeyRecovery +1.3.6.1.4.1.311.10.3.12 = Ms-DocumentSigning +1.3.6.1.4.1.311.10.3.13 = Ms-LifetimeSigning +1.3.6.1.4.1.311.10.5.1 = Ms-Drm +1.3.6.1.4.1.311.10.5.2 = Ms-DrmIndividualization +1.3.6.1.4.1.311.10.9.1 = Ms-CrossCertDistPoints +1.3.6.1.4.1.311.12 = Ms-Catalog +1.3.6.1.4.1.311.12.1 = Ms-Catalog-dot-1 +1.3.6.1.4.1.311.12.1.1 = Ms-CatalogList +1.3.6.1.4.1.311.12.1.2 = Ms-CatalogListMember +1.3.6.1.4.1.311.12.2 = Ms-Catalog-dot-2 +1.3.6.1.4.1.311.12.2.1 = Ms-CatNameValue +1.3.6.1.4.1.311.12.2.2 = Ms-CatMemberInfo +1.3.6.1.4.1.311.15 = Ms-Java +1.3.6.1.4.1.311.16 = Ms-Outlook-Exchange +1.3.6.1.4.1.311.17 = Ms-PKCS12-attribute +1.3.6.1.4.1.311.17.1 = Ms-PKCS12-LocalMachineKeyset +1.3.6.1.4.1.311.20.1 = Ms-AutoEnrollCtlUsage +1.3.6.1.4.1.311.20.2 = Ms-EnrollCerttypeExtension +1.3.6.1.4.1.311.21 = Ms-CertSrv-Infrastructure +1.3.6.1.4.1.311.21.1 = Ms-CaKeyCertIndexPair +1.3.6.1.4.1.311.21.2 = Ms-CertSrvPreviousCertHash +1.3.6.1.4.1.311.21.3 = Ms-CrlVirtualBase +1.3.6.1.4.1.311.21.4 = Ms-CrlNextPublish +1.3.6.1.4.1.311.21.6 = Ms-KeyRecovery +1.3.6.1.4.1.311.21.7 = Ms-CertificateTemplate +1.3.6.1.4.1.311.21.9 = Ms-DummySigner +1.3.6.1.4.1.311.40 = Ms-Fonts +1.3.6.1.4.1.311.41 = Ms-License-and-Registration +1.3.6.1.4.1.311.42 = Ms-CorpPKI +1.3.6.1.4.1.311.60 = Ms-ev +1.3.6.1.4.1.311.60.1 = Ms-ev-rootProgram +1.3.6.1.4.1.311.60.1.1 = Ms-ev-rootProgramFlags +1.3.6.1.4.1.311.60.2 = Ms-ev-2 +1.3.6.1.4.1.311.60.2.1 = Ms-ev-2-1 +1.3.6.1.4.1.311.60.2.1.1 = Ms-jurisdictionOfIncorporationLocalityName +1.3.6.1.4.1.311.60.2.1.2 = Ms-jurisdictionOfIncorporationStateOrProvinceNm +1.3.6.1.4.1.311.60.2.1.3 = Ms-jurisdictionOfIncorporationCountryName +1.3.6.1.4.1.311.88 = Ms-CapiCom +1.3.6.1.5 = security +1.3.6.1.5.5 = security-Mechanisms +1.3.6.1.5.5.7 = public-key-infrastructure +1.3.6.1.5.5.7.1.1 = pkix-AuthorityInfoAccess +1.3.6.1.5.5.7.1.12 = pkix-LogoType +1.3.6.1.5.5.7.2.1 = id-qt-CPS +1.3.6.1.5.5.7.2.2 = id-qt-UNotice +1.3.6.1.5.5.7.2.3 = id-qt-TextNotice +1.3.6.1.5.5.7.2.4 = id-qt-ACPS +1.3.6.1.5.5.7.2.5 = id-qt-ACUNotice +1.3.6.1.5.5.7.3.1 = id-kp-ServerAuth +1.3.6.1.5.5.7.3.2 = id-kp-ClientAuth +1.3.6.1.5.5.7.3.3 = id-kp-CodeSigning +1.3.6.1.5.5.7.3.4 = id-kp-EmailProtection +1.3.6.1.5.5.7.3.5 = id-kp-IPSecEndSystem +1.3.6.1.5.5.7.3.6 = id-kp-IPSecTunnel +1.3.6.1.5.5.7.3.7 = id-kp-IPSecUser +1.3.6.1.5.5.7.3.8 = id-kp-Timestamping +1.3.6.1.5.5.7.3.9 = id-kp-OCSPSigning +1.3.6.1.5.5.7.3.10 = id-kp-DVCS +1.3.6.1.5.5.7.3.11 = id-kp-SBGPCertAAServiceAuth +1.3.6.1.5.5.7.3.13 = id-kp-EAPOverPPP +1.3.6.1.5.5.7.3.14 = id-kp-EAPOverLAN +1.3.14.3 = oiw-ssig +1.3.14.3.2 = oiw-ssig-algorithms +1.3.14.3.2.2 = oiw-ssig-md4WithRsa +1.3.14.3.2.3 = oiw-ssig-md5WithRsa +1.3.14.3.2.4 = oiw-ssig-md4WithRsaEncryption +1.3.14.3.2.14 = oiw-ssig-mdc2WithRsaSignature +1.3.14.3.2.15 = oiw-ssig-sha0WithRsaSignature +1.3.14.3.2.18 = oiw-ssig-sha0 +1.3.14.3.2.19 = oiw-ssig-mdc2 +1.3.14.3.2.21 = oiw-ssig-dsaCommonWithSha0 +1.3.14.3.2.24 = oiw-ssig-md2WithRsaSignature +1.3.14.3.2.25 = oiw-ssig-md5WithRsaSignature +1.3.14.3.2.26 = oiw-ssig-sha1 +1.3.14.3.2.29 = oiw-ssig-sha1WithRsaEncryption +2 = joint-iso/itu-t +2.1 = asn.1 +2.5 = x500 +2.5.4 = x500-attribute-types +2.5.4.3 = x500-CommonName +2.5.4.6 = x500-CountryName +2.5.4.7 = x500-LocalityName +2.5.4.8 = x500-StatOrProvinceName +2.5.4.10 = x500-OrganizationName +2.5.4.11 = x500-OrganizationUnitName +2.5.29 = id-ce +2.5.29.1 = id-ce-AuthorityKeyIdentifier-Deprecated +2.5.29.2 = id-ce-KeyAttributes-Deprecated +2.5.29.3 = id-ce-CertificatePolicies-Deprecated +2.5.29.4 = id-ce-KeyUsageRestriction-Deprecated +2.5.29.7 = id-ce-SubjectAltName-Deprecated +2.5.29.8 = id-ce-IssuerAltName-Deprecated +2.5.29.14 = id-ce-SubjectKeyIdentifier +2.5.29.15 = id-ce-KeyUsage +2.5.29.16 = id-ce-PrivateKeyUsagePeriod +2.5.29.17 = id-ce-SubjectAltName +2.5.29.18 = id-ce-issuerAltName +2.5.29.19 = id-ce-BasicConstraints +2.5.29.25 = id-ce-CrlDistributionPoints +2.5.29.29 = id-ce-CertificateIssuer +2.5.29.30 = id-ce-NameConstraints +2.5.29.31 = id-ce-CrlDistributionPoints +2.5.29.32 = id-ce-CertificatePolicies +2.5.29.32.0 = id-ce-cp-anyPolicy +2.5.29.35 = id-ce-AuthorityKeyIdentifier +2.5.29.36 = id-ce-PolicyConstraints +2.5.29.37 = id-ce-ExtKeyUsage +2.16 = by-country +2.16.840 = usa +2.16.840.1 = usa-company-arc +2.16.840.1.101 = usa-goverment +2.16.840.1.101.3 = usa-computer-security-register +2.16.840.1.101.3.4 = nist-algorithms +2.16.840.1.101.3.4.1 = nist-aes-algorithms +2.16.840.1.101.3.4.2 = nist-hash-algorithms +2.16.840.1.101.3.4.2.1 = nist-sha256 +2.16.840.1.101.3.4.2.2 = nist-sha384 +2.16.840.1.101.3.4.2.3 = nist-sha512 +2.16.840.1.101.3.4.2.4 = nist-sha224 +2.16.840.1.101.3.4.2.5 = nist-sha512-224 +2.16.840.1.101.3.4.2.6 = nist-sha512-256 +2.16.840.1.101.3.4.2.7 = nist-sha3-224 +2.16.840.1.101.3.4.2.8 = nist-sha3-256 +2.16.840.1.101.3.4.2.9 = nist-sha3-384 +2.16.840.1.101.3.4.2.10 = nist-sha3-512 +2.16.840.1.101.3.4.2.11 = nist-shake128 +2.16.840.1.101.3.4.2.12 = nist-shake256 +2.16.840.1.101.3.4.3 = nist-sigAlgs +2.16.840.1.101.3.4.3.1 = nist-dsa-with-sha224 +2.16.840.1.101.3.4.3.2 = nist-dsa-with-sha256 +2.16.840.1.101.3.4.3.3 = nist-dsa-with-sha384 +2.16.840.1.101.3.4.3.4 = nist-dsa-with-sha512 +2.16.840.1.101.3.4.3.5 = nist-id-dsa-with-sha224 +2.16.840.1.101.3.4.3.6 = nist-id-dsa-with-sha256 +2.16.840.1.101.3.4.3.7 = nist-id-dsa-with-sha384 +2.16.840.1.101.3.4.3.8 = nist-id-dsa-with-sha512 +2.16.840.1.101.3.4.3.9 = nist-id-ecdsa-with-sha3-224 +2.16.840.1.101.3.4.3.10 = nist-id-ecdsa-with-sha3-256 +2.16.840.1.101.3.4.3.11 = nist-id-ecdsa-with-sha3-384 +2.16.840.1.101.3.4.3.12 = nist-id-ecdsa-with-sha3-512 +2.16.840.1.101.3.4.3.13 = nist-id-rsassa-pkcs1-v1-5-with-sha3-224 +2.16.840.1.101.3.4.3.14 = nist-id-rsassa-pkcs1-v1-5-with-sha3-256 +2.16.840.1.101.3.4.3.15 = nist-id-rsassa-pkcs1-v1-5-with-sha3-384 +2.16.840.1.101.3.4.3.16 = nist-id-rsassa-pkcs1-v1-5-with-sha3-512 +2.16.840.1.113730 = netscape +2.16.840.1.113730.1.1 = netscape-cert-type +2.16.840.1.113730.1.2 = netscape-base-url +2.16.840.1.113730.1.3 = netscape-revocation-url +2.16.840.1.113730.1.4 = netscape-ca-revocation-url +2.16.840.1.113730.1.7 = netscape-cert-renewal-url +2.16.840.1.113730.1.8 = netscape-ca-policy-url +2.16.840.1.113730.1.9 = netscape-HomePage-url +2.16.840.1.113730.1.10 = netscape-EntityLogo +2.16.840.1.113730.1.11 = netscape-UserPicture +2.16.840.1.113730.1.12 = netscape-ssl-server-name +2.16.840.1.113730.1.13 = netscape-comment +2.16.840.1.113730.4.1 = netscape-eku-serverGatedCrypto +2.16.840.1.113733 = verisign +2.16.840.1.113733.1 = verisign-pki +2.16.840.1.113733.1.6 = verisign-pki-extensions +2.16.840.1.113733.1.6.7 = verisign-pki-ext-RolloverID +2.16.840.1.113733.1.7 = verisign-pki-policies +2.16.840.1.113733.1.7.9 = verisign-pki-policy-9 +2.16.840.1.113733.1.7.21 = verisign-pki-policy-21 +2.16.840.1.113733.1.7.23 = verisign-pki-policy-vtn-cp +2.16.840.1.113733.1.7.23.1 = verisign-pki-policy-vtn-cp-class1 +2.16.840.1.113733.1.7.23.2 = verisign-pki-policy-vtn-cp-class2 +2.16.840.1.113733.1.7.23.3 = verisign-pki-policy-vtn-cp-class3 +2.16.840.1.113733.1.7.23.4 = verisign-pki-policy-vtn-cp-class4 +2.16.840.1.113733.1.7.23.6 = verisign-pki-policy-vtn-cp-6 +2.16.840.1.113733.1.7.46 = verisign-pki-policy-cis +2.16.840.1.113733.1.7.46.1 = verisign-pki-policy-cis-type1 +2.16.840.1.113733.1.7.46.2 = verisign-pki-policy-cis-type2 +2.16.840.1.113733.1.7.48 = verisign-pki-policy-thawte +2.16.840.1.113733.1.7.48.1 = verisign-pki-policy-thawte-cps-1 +# check the following one... +2.16.840.1.113733.1.8.1 = verisign-pki-eku-IssStrongCrypto + diff --git a/src/VBox/Runtime/common/asn1/oiddb2c.cpp b/src/VBox/Runtime/common/asn1/oiddb2c.cpp new file mode 100644 index 00000000..4fcb92cf --- /dev/null +++ b/src/VBox/Runtime/common/asn1/oiddb2c.cpp @@ -0,0 +1,635 @@ +/* $Id: oiddb2c.cpp $ */ +/** @file + * IPRT - OID text database to C converter. + * + * The output is used by asn1-dump.cpp. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/types.h> +#include <iprt/ctype.h> +#include <iprt/stdarg.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +/* + * Include the string table code. + */ +#define BLDPROG_STRTAB_MAX_STRLEN 48 +#define BLDPROG_STRTAB_WITH_COMPRESSION +#define BLDPROG_STRTAB_PURE_ASCII +#define BLDPROG_STRTAB_WITH_CAMEL_WORDS +#include <iprt/bldprog-strtab-template.cpp.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define OID2C_MAX_COMP_VALUE _1G + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Raw OID tree node. + * + * This is what we produce while loading OID input files. + */ +typedef struct RAWOIDNODE +{ + /** The component value. */ + uint32_t uKey; + /** Number of children. */ + uint32_t cChildren; + /** Pointer to the children pointers (sorted by key). */ + struct RAWOIDNODE **papChildren; + /** Pointer to the parent. */ + struct RAWOIDNODE *pParent; + /** The string table entry for this node. */ + BLDPROGSTRING StrTabEntry; + /** The table index of the children. */ + uint32_t idxChildren; + /** Set if we've got one or more children with large keys. */ + bool fChildrenInBigTable; +} RAWOIDNODE; +/** Pointer to a raw OID node. */ +typedef RAWOIDNODE *PRAWOIDNODE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** What to prefix errors with. */ +static const char *g_pszProgName = "oiddb2c"; + +/** The OID tree. */ +static PRAWOIDNODE g_pOidRoot = NULL; +/** Number of nodes in the OID tree. */ +static uint32_t g_cOidNodes = 0; +/** Number of nodes in the OID tree that has strings (for the string table). */ +static uint32_t g_cOidNodesWithStrings = 0; +/** Max number of children of a node in the OID tree. */ +static uint32_t g_cMaxOidChildren = 0; +/** Number of nodes which key fits within 6-bits. */ +static uint32_t g_cOidNodiesWith6bitKeys = 0; + + +static RTEXITCODE error(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + fprintf(stderr, "%s: error: ", g_pszProgName); + vfprintf(stderr, pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + +static RTEXITCODE warning(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + fprintf(stderr, "%s: warning: ", g_pszProgName); + vfprintf(stderr, pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + + +static void writeDottedOidForNode(PRAWOIDNODE pCurNode, FILE *pOut) +{ + if (pCurNode->pParent) + { + writeDottedOidForNode(pCurNode->pParent, pOut); + fprintf(pOut, ".%u", pCurNode->uKey); + } + else + fprintf(pOut, "%u", pCurNode->uKey); +} + + +static void writeOidTree(PRAWOIDNODE pCurNode, FILE *pOut, bool fBigTable, PBLDPROGSTRTAB pStrTab) +{ + /* + * First we produce the entries for our children. + */ + if (pCurNode->fChildrenInBigTable == fBigTable) + { + for (unsigned i = 0; i < pCurNode->cChildren; i++) + { + PRAWOIDNODE pChild = pCurNode->papChildren[i]; + fprintf(pOut, " { %*u, %2u, %u, %2u, %4u, %#06x }, /* ", + fBigTable ? 7 : 2, + pChild->uKey, + (unsigned)pChild->StrTabEntry.cchString, + pChild->fChildrenInBigTable, + pChild->cChildren, + pChild->idxChildren, + pChild->StrTabEntry.offStrTab); + writeDottedOidForNode(pChild, pOut); + if (pChild->StrTabEntry.pszString) + { + fputs(" = \"", pOut); + BldProgStrTab_PrintCStringLitteral(pStrTab, &pChild->StrTabEntry, pOut); + fputs("\" */\n", pOut); + } + else + fputs(" */\n", pOut); + } + } + + /* + * Then we decend and let our children do the same. + */ + for (unsigned i = 0; i < pCurNode->cChildren; i++) + writeOidTree(pCurNode->papChildren[i], pOut, fBigTable, pStrTab); +} + + +static uint32_t prepareOidTreeForWriting(PRAWOIDNODE pCurNode, uint32_t idxCur, bool fBigTable) +{ + if (pCurNode->fChildrenInBigTable == fBigTable) + { + pCurNode->idxChildren = pCurNode->cChildren ? idxCur : 0; + idxCur += pCurNode->cChildren; + } + + for (unsigned i = 0; i < pCurNode->cChildren; i++) + idxCur = prepareOidTreeForWriting(pCurNode->papChildren[i], idxCur, fBigTable); + + return idxCur; +} + + +static void addStringFromOidTree(PRAWOIDNODE pCurNode, PBLDPROGSTRTAB pStrTab) +{ + /* Do self. */ + if (pCurNode->StrTabEntry.pszString) + BldProgStrTab_AddString(pStrTab, &pCurNode->StrTabEntry); + + /* Recurse into children. */ + unsigned i = pCurNode->cChildren; + while (i-- > 0) + addStringFromOidTree(pCurNode->papChildren[i], pStrTab); +} + + +static bool isNiceAsciiString(const char *psz) +{ + unsigned uch; + while ((uch = *psz) != '\0') + if ( !(uch & 0x80) + && ( uch >= 0x20 + || uch == '\t') ) + psz++; + else + return false; + return true; +} + + +static RTEXITCODE addOidToTree(uint32_t const *pauComponents, unsigned cComponents, const char *pszName, + const char *pszFile, unsigned iLineNo) +{ + /* + * Check preconditions. + */ + size_t cchName = strlen(pszName); + if (cchName == '\0') + return warning("%s(%d): Empty OID name!\n", pszFile, iLineNo); + if (cchName >= BLDPROG_STRTAB_MAX_STRLEN) + return warning("%s(%d): OID name is too long (%u)!\n", pszFile, iLineNo, (unsigned)cchName); + if (cComponents == 0) + return warning("%s(%d): 'Description' without valid OID preceeding it!\n", pszFile, iLineNo); + if (!isNiceAsciiString(pszName)) + return warning("%s(%d): Contains unwanted characters!\n", pszFile, iLineNo); + + /* + * Make sure we've got a root node (it has no actual OID componet value, + * it's just a place to put the top level children). + */ + if (!g_pOidRoot) + { + g_pOidRoot = (PRAWOIDNODE)calloc(sizeof(*g_pOidRoot), 1); + if (!g_pOidRoot) + return error("Out of memory!\n"); + } + + /* + * Decend into the tree, adding any missing nodes as we go along. + * We'll end up with the node which is being named. + */ + PRAWOIDNODE pCur = g_pOidRoot; + while (cComponents-- > 0) + { + uint32_t const uKey = *pauComponents++; + uint32_t i = pCur->cChildren; + while ( i > 0 + && pCur->papChildren[i - 1]->uKey >= uKey) + i--; + if ( i < pCur->cChildren + && pCur->papChildren[i]->uKey == uKey) + pCur = pCur->papChildren[i]; + else + { + /* Resize the child pointer array? */ + if ((pCur->cChildren % 16) == 0) + { + void *pvNew = realloc(pCur->papChildren, sizeof(pCur->papChildren[0]) * (pCur->cChildren + 16)); + if (!pvNew) + return error("Out of memory!\n"); + pCur->papChildren = (PRAWOIDNODE *)pvNew; + } + + /* Allocate and initialize the node. */ + PRAWOIDNODE pNew = (PRAWOIDNODE)malloc(sizeof(*pNew)); + if (!pNew) + return error("Out of memory!\n"); + pNew->uKey = uKey; + pNew->pParent = pCur; + pNew->papChildren = NULL; + pNew->cChildren = 0; + pNew->fChildrenInBigTable = false; + memset(&pNew->StrTabEntry, 0, sizeof(pNew->StrTabEntry)); + + /* Insert it. */ + if (i < pCur->cChildren) + memmove(&pCur->papChildren[i + 1], &pCur->papChildren[i], (pCur->cChildren - i) * sizeof(pCur->papChildren[0])); + pCur->papChildren[i] = pNew; + pCur->cChildren++; + + if (pCur->cChildren > g_cMaxOidChildren) + g_cMaxOidChildren = pCur->cChildren; + g_cOidNodes++; + if (uKey < 64) + g_cOidNodiesWith6bitKeys++; + else + { + pCur->fChildrenInBigTable = true; + if (!pCur->pParent) + return error("Invalid OID! Top level componet value is out of range: %u (max 2)\n", uKey); + } + + /* Decend (could optimize insertion of the remaining nodes, but + too much work for very little gain). */ + pCur = pNew; + } + } + + /* + * Update the node. + */ + if (!pCur->StrTabEntry.pszString) + { + pCur->StrTabEntry.pszString = (char *)malloc(cchName + 1); + if (pCur->StrTabEntry.pszString) + memcpy(pCur->StrTabEntry.pszString, pszName, cchName + 1); + else + return error("Out of memory!\n"); + pCur->StrTabEntry.cchString = cchName; + if (cchName >= 64) + pCur->fChildrenInBigTable = true; + g_cOidNodesWithStrings++; + } + /* Ignore duplicates, but warn if different name. */ + else if ( pCur->StrTabEntry.cchString != cchName + || strcmp(pszName, pCur->StrTabEntry.pszString) != 0) + warning("%s(%d): Duplicate OID, name differs: '%s' vs '%s'\n", pszFile, iLineNo, pCur->StrTabEntry.pszString, pszName); + + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE parseOid(uint32_t *pauComponents, unsigned *pcComponents, unsigned cMaxComponents, char const *pszOid, + const char *pszFile, unsigned iLine) +{ + const char *pszInput = pszOid; + unsigned i = 0; + char ch; + for (;;) + { + /* + * Parse the value. + */ + unsigned uValue = 0; + if (RT_C_IS_DIGIT((ch = *pszOid))) + { + do + { + uValue *= 10; + uValue += ch - '0'; + if (uValue < OID2C_MAX_COMP_VALUE) + pszOid++; + else + return warning("%s(%d): Component %u in OID attribute value '%s' is out side the supported!\n", + pszFile, iLine, i, pszInput); + } while (RT_C_IS_DIGIT((ch = *pszOid))); + if ( ch == '\0' + || ch == '.' + || RT_C_IS_BLANK(ch)) + { + if (i < cMaxComponents) + { + pauComponents[i] = uValue; + i++; + if (ch != '\0') + pszOid++; + else + { + *pcComponents = i; + return RTEXITCODE_SUCCESS; + } + } + else + return warning("%s(%d): Too many OID components in '%s'!\n", pszFile, iLine, pszInput); + } + else + return warning("%s(%d): Invalid OID attribute value '%s' (ch=%c)!\n", pszFile, iLine, pszInput, ch); + } + else + return warning("%s(%d): Invalid OID attribute value '%s' (ch=%c)!\n", pszFile, iLine, pszInput, ch); + } +} + + +static RTEXITCODE loadOidFile(FILE *pIn, const char *pszFile) +{ + /* + * We share the format used by dumpasn1.cfg, except that we accept + * dotted OIDs. + * + * An OID entry starts with a 'OID = <space or dot separated OID>'. + * It is usually followed by an 'Comment = ', which we ignore, and a + * 'Description = <name>' which we keep. We save the entry once we + * see the description attribute. + */ + unsigned cOidComponents = 0; + uint32_t auOidComponents[16]; + unsigned iLineNo = 0; + char szLine[16384]; + char *pszLine; + szLine[sizeof(szLine) - 1] = '\0'; + while ((pszLine = fgets(szLine, sizeof(szLine) - 1, pIn)) != NULL) + { + iLineNo++; + + /* Strip leading spaces.*/ + char ch; + while (RT_C_IS_SPACE((ch = *pszLine)) ) + pszLine++; + + /* We only care about lines starting with 'OID =', 'Description =' or + a numbered OID. */ + if ( ch == 'O' || ch == 'o' + || ch == 'D' || ch == 'd' + || ch == '0' || ch == '1' || ch == '2') + { + /* Right strip the line. */ + size_t cchLine = strlen(pszLine); + while (cchLine > 0 && RT_C_IS_SPACE(pszLine[cchLine - 1])) + cchLine--; + pszLine[cchLine] = '\0'; + + /* Separate the attribute name from the value. */ + char *pszValue = (char *)memchr(pszLine, '=', cchLine); + if (pszValue) + { + size_t cchName = pszValue - pszLine; + + /* Right strip the name. */ + while (cchName > 0 && RT_C_IS_SPACE(pszLine[cchName - 1])) + cchName--; + pszLine[cchName] = '\0'; + + /* Left strip the value. */ + do + pszValue++; + while (RT_C_IS_SPACE(*pszValue)); + + /* Attribute switch */ + if ( cchName == 3 + && (pszLine[0] == 'O' || pszLine[0] == 'o') + && (pszLine[1] == 'I' || pszLine[1] == 'i') + && (pszLine[2] == 'D' || pszLine[2] == 'd')) + { + cOidComponents = 0; + parseOid(auOidComponents, &cOidComponents, RT_ELEMENTS(auOidComponents), pszValue, pszFile, iLineNo); + } + else if ( cchName == 11 + && (pszLine[0] == 'D' || pszLine[0] == 'd') + && (pszLine[1] == 'e' || pszLine[1] == 'E') + && (pszLine[2] == 's' || pszLine[2] == 'S') + && (pszLine[3] == 'c' || pszLine[3] == 'C') + && (pszLine[4] == 'r' || pszLine[4] == 'R') + && (pszLine[5] == 'i' || pszLine[5] == 'I') + && (pszLine[6] == 'p' || pszLine[6] == 'P') + && (pszLine[7] == 't' || pszLine[7] == 'T') + && (pszLine[8] == 'i' || pszLine[8] == 'I') + && (pszLine[9] == 'o' || pszLine[9] == 'O') + && (pszLine[10] == 'n' || pszLine[10] == 'N')) + { + if ( addOidToTree(auOidComponents, cOidComponents, pszValue, pszFile, iLineNo) + != RTEXITCODE_SUCCESS) + return RTEXITCODE_FAILURE; + cOidComponents = 0; + } + else + { + /* <OID> = <Value> */ + cOidComponents = 0; + if ( parseOid(auOidComponents, &cOidComponents, RT_ELEMENTS(auOidComponents), pszLine, pszLine, iLineNo) + == RTEXITCODE_SUCCESS) + { + if ( addOidToTree(auOidComponents, cOidComponents, pszValue, pszFile, iLineNo) + != RTEXITCODE_SUCCESS) + return RTEXITCODE_FAILURE; + } + cOidComponents = 0; + } + } + } + + } + if (feof(pIn)) + return RTEXITCODE_SUCCESS; + return error("error or something reading '%s'.\n", pszFile); +} + + + +static RTEXITCODE usage(FILE *pOut, const char *argv0, RTEXITCODE rcExit) +{ + fprintf(pOut, "usage: %s <out-file.c> <oid-file> [oid-file2 [...]]\n", argv0); + return rcExit; +} + +int main(int argc, char **argv) +{ + /* + * Process arguments and input files. + */ + bool fVerbose = false; + unsigned cInFiles = 0; + const char *pszOutFile = NULL; + for (int i = 1; i < argc; i++) + { + const char *pszFile = NULL; + if (argv[i][0] != '-') + pszFile = argv[i]; + else if (!strcmp(argv[i], "-")) + pszFile = argv[i]; + else + return usage(stderr, argv[0], RTEXITCODE_SYNTAX); + + if (!pszOutFile) + pszOutFile = pszFile; + else + { + cInFiles++; + FILE *pInFile = fopen(pszFile, "r"); + if (!pInFile) + return error("opening '%s' for reading.\n", pszFile); + RTEXITCODE rcExit = loadOidFile(pInFile, pszFile); + fclose(pInFile); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + } + + /* + * Check that the user specified at least one input and an output file. + */ + if (!pszOutFile) + return error("No output file specified specified!\n"); + if (!cInFiles) + return error("No input files specified!\n"); + if (!g_cOidNodes) + return error("No OID found!\n"); + if (fVerbose) + printf("debug: %u nodes with strings; %u nodes without strings; %u nodes in total;\n" + "debug: max %u children; %u nodes with 6-bit keys (%u others)\n", + g_cOidNodesWithStrings, g_cOidNodes - g_cOidNodesWithStrings, g_cOidNodes, + g_cMaxOidChildren, g_cOidNodiesWith6bitKeys, g_cOidNodes - g_cOidNodiesWith6bitKeys); + + /* + * Compile the string table. + */ + BLDPROGSTRTAB StrTab; + if (!BldProgStrTab_Init(&StrTab, g_cOidNodesWithStrings)) + return error("Out of memory!\n"); + + addStringFromOidTree(g_pOidRoot, &StrTab); + + if (!BldProgStrTab_CompileIt(&StrTab, fVerbose)) + return error("BldProgStrTab_CompileIt failed!\n"); + + /* + * Open the output file and write out the stuff. + */ + FILE *pOut; + if (!strcmp(pszOutFile, "-")) + pOut = stdout; + else + pOut = fopen(pszOutFile, "w"); + if (!pOut) + return error("opening '%s' for writing.\n", pszOutFile); + + /* Write the string table. */ + BldProgStrTab_WriteStringTable(&StrTab, pOut, "static ", "g_", "OidDbStrTab"); + + prepareOidTreeForWriting(g_pOidRoot, 0, false /*fBigTable*/); + prepareOidTreeForWriting(g_pOidRoot, 0, true /*fBigTable*/); + + fprintf(pOut, + "\n" + "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n" + "# pragma pack(2)\n" + "#endif\n" + "typedef struct RTOIDENTRYSMALL\n" + "{\n" + " uint32_t uKey : 6;\n" + " uint32_t cchString : 6;\n" + " uint32_t fBigTable : 1;\n" + " uint32_t cChildren : 7;\n" + " uint32_t idxChildren : 12;\n" + " uint16_t offString;\n" + "} RTOIDENTRYSMALL;\n" + "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n" + "# pragma pack()\n" + "AssertCompileSize(RTOIDENTRYSMALL, 6);\n" + "#endif\n" + "typedef RTOIDENTRYSMALL const *PCRTOIDENTRYSMALL;\n" + "\n" + "static const RTOIDENTRYSMALL g_aSmallOidTable[] = \n{\n"); + writeOidTree(g_pOidRoot, pOut, false /*fBigTable*/, &StrTab); + fprintf(pOut, "};\n"); + + fprintf(pOut, + "\n" + "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n" + "# pragma pack(2)\n" + "#endif\n" + "typedef struct RTOIDENTRYBIG\n" + "{\n" + " uint32_t uKey;\n" + " uint8_t cchString;\n" + " uint8_t fBigTable : 1;\n" + " uint8_t cChildren : 7;\n" + " uint16_t idxChildren;\n" + " uint16_t offString;\n" + "} RTOIDENTRYBIG;\n" + "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n" + "# pragma pack()\n" + "AssertCompileSize(RTOIDENTRYBIG, 10);\n" + "#endif\n" + "typedef RTOIDENTRYBIG const *PCRTOIDENTRYBIG;\n" + "\n" + "static const RTOIDENTRYBIG g_aBigOidTable[] = \n{\n"); + writeOidTree(g_pOidRoot, pOut, true /*fBigTable*/, &StrTab); + fprintf(pOut, "};\n"); + + /* Carefully close the output file. */ + if (ferror(pOut)) + return error("problem writing '%s'!\n", pszOutFile); + if (fclose(pOut) != 0) + return error("closing '%s' after writing it.\n", pszOutFile); + + return RTEXITCODE_SUCCESS; +} + |