summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/asn1/asn1-encode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common/asn1/asn1-encode.cpp')
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-encode.cpp535
1 files changed, 535 insertions, 0 deletions
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;
+}
+