diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/utils/asn1 | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'winpr/libwinpr/utils/asn1')
-rw-r--r-- | winpr/libwinpr/utils/asn1/asn1.c | 1490 |
1 files changed, 1490 insertions, 0 deletions
diff --git a/winpr/libwinpr/utils/asn1/asn1.c b/winpr/libwinpr/utils/asn1/asn1.c new file mode 100644 index 0000000..201f23c --- /dev/null +++ b/winpr/libwinpr/utils/asn1/asn1.c @@ -0,0 +1,1490 @@ +/** + * WinPR: Windows Portable Runtime + * ASN1 routines + * + * Copyright 2022 David Fort <contact@hardening-consulting.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/asn1.h> +#include <winpr/wlog.h> +#include <winpr/crt.h> + +#include "../../log.h" +#define TAG WINPR_TAG("asn1") + +typedef struct +{ + size_t poolOffset; + size_t capacity; + size_t used; +} Asn1Chunk; + +#define MAX_STATIC_ITEMS 50 + +/** @brief type of encoder container */ +typedef enum +{ + ASN1_CONTAINER_SEQ, + ASN1_CONTAINER_SET, + ASN1_CONTAINER_APP, + ASN1_CONTAINER_CONTEXT_ONLY, + ASN1_CONTAINER_OCTETSTRING, +} ContainerType; + +typedef struct WinPrAsn1EncContainer WinPrAsn1EncContainer; +/** @brief a container in the ASN1 stream (sequence, set, app or contextual) */ +struct WinPrAsn1EncContainer +{ + size_t headerChunkId; + BOOL contextual; + WinPrAsn1_tag tag; + ContainerType containerType; +}; + +/** @brief the encoder internal state */ +struct WinPrAsn1Encoder +{ + WinPrAsn1EncodingRule encoding; + wStream* pool; + + Asn1Chunk* chunks; + Asn1Chunk staticChunks[MAX_STATIC_ITEMS]; + size_t freeChunkId; + size_t chunksCapacity; + + WinPrAsn1EncContainer* containers; + WinPrAsn1EncContainer staticContainers[MAX_STATIC_ITEMS]; + size_t freeContainerIndex; + size_t containerCapacity; +}; + +#define WINPR_ASSERT_VALID_TAG(t) WINPR_ASSERT(t < 64) + +void WinPrAsn1FreeOID(WinPrAsn1_OID* poid) +{ + WINPR_ASSERT(poid); + free(poid->data); + poid->data = NULL; + poid->len = 0; +} + +void WinPrAsn1FreeOctetString(WinPrAsn1_OctetString* octets) +{ + WinPrAsn1FreeOID(octets); +} + +/** + * The encoder is implemented with the goals to: + * * have an API which is convenient to use (avoid computing inner elements size) + * * hide the BER/DER encoding details + * * avoid multiple copies and memory moves when building the content + * + * To achieve this, the encoder contains a big memory block (encoder->pool), and various chunks + * (encoder->chunks) pointing to that memory block. The idea is to reserve some space in the pool + * for the container headers when we start a new container element. For example when a sequence is + * started we reserve 6 bytes which is the maximum size: byte0 + length. Then fill the content of + * the sequence in further chunks. When a container is closed, we compute the inner size (by adding + * the size of inner chunks), we write the headers bytes, and we adjust the chunk size accordingly. + * + * For example to encode: + * SEQ + * IASTRING(test1) + * INTEGER(200) + * + * with this code: + * + * WinPrAsn1EncSeqContainer(enc); + * WinPrAsn1EncIA5String(enc, "test1"); + * WinPrAsn1EncInteger(enc, 200); + * + * Memory pool and chunks would look like: + * + * [ reserved for seq][string|5|"test1"][integer|0x81|200] + * (6 bytes) + * |-----------------||----------------------------------| + * ^ ^ + * | | + * chunk0 chunk1 + * + * As we try to compact chunks as much as we can, we managed to encode the ia5string and the + * integer using the same chunk. + * + * When the sequence is closed with: + * + * WinPrAsn1EncEndContainer(enc); + * + * The final pool and chunks will look like: + * + * XXXXXX[seq headers][string|5|"test1"][integer|0x81|200] + * + * |-----------||----------------------------------| + * ^ ^ + * | | + * chunk0 chunk1 + * + * The generated content can be retrieved using: + * + * WinPrAsn1EncToStream(enc, targetStream); + * + * It will sequentially write all the chunks in the given target stream. + */ + +WinPrAsn1Encoder* WinPrAsn1Encoder_New(WinPrAsn1EncodingRule encoding) +{ + WinPrAsn1Encoder* enc = calloc(1, sizeof(*enc)); + if (!enc) + return NULL; + + enc->encoding = encoding; + enc->pool = Stream_New(NULL, 1024); + if (!enc->pool) + { + free(enc); + return NULL; + } + + enc->containers = &enc->staticContainers[0]; + enc->chunks = &enc->staticChunks[0]; + enc->chunksCapacity = MAX_STATIC_ITEMS; + enc->freeContainerIndex = 0; + return enc; +} + +void WinPrAsn1Encoder_Reset(WinPrAsn1Encoder* enc) +{ + WINPR_ASSERT(enc); + + enc->freeContainerIndex = 0; + enc->freeChunkId = 0; + + ZeroMemory(enc->chunks, sizeof(*enc->chunks) * enc->chunksCapacity); +} + +void WinPrAsn1Encoder_Free(WinPrAsn1Encoder** penc) +{ + WinPrAsn1Encoder* enc = NULL; + + WINPR_ASSERT(penc); + enc = *penc; + if (enc) + { + if (enc->containers != &enc->staticContainers[0]) + free(enc->containers); + + if (enc->chunks != &enc->staticChunks[0]) + free(enc->chunks); + + Stream_Free(enc->pool, TRUE); + free(enc); + } + *penc = NULL; +} + +static Asn1Chunk* asn1enc_get_free_chunk(WinPrAsn1Encoder* enc, size_t chunkSz, BOOL commit, + size_t* id) +{ + Asn1Chunk* ret = NULL; + WINPR_ASSERT(enc); + WINPR_ASSERT(chunkSz); + + if (commit) + { + /* if it's not a reservation let's see if the last chunk is not a reservation and can be + * expanded */ + size_t lastChunk = enc->freeChunkId ? enc->freeChunkId - 1 : 0; + ret = &enc->chunks[lastChunk]; + if (ret->capacity && ret->capacity == ret->used) + { + if (!Stream_EnsureRemainingCapacity(enc->pool, chunkSz)) + return NULL; + + Stream_Seek(enc->pool, chunkSz); + ret->capacity += chunkSz; + ret->used += chunkSz; + if (id) + *id = lastChunk; + return ret; + } + } + + if (enc->freeChunkId == enc->chunksCapacity) + { + /* chunks need a resize */ + Asn1Chunk* src = (enc->chunks != &enc->staticChunks[0]) ? enc->chunks : NULL; + Asn1Chunk* tmp = realloc(src, (enc->chunksCapacity + 10) * sizeof(*src)); + if (!tmp) + return NULL; + + if (enc->chunks == &enc->staticChunks[0]) + memcpy(tmp, &enc->staticChunks[0], enc->chunksCapacity * sizeof(*src)); + else + memset(tmp + enc->freeChunkId, 0, sizeof(*tmp) * 10); + + enc->chunks = tmp; + enc->chunksCapacity += 10; + } + if (enc->freeChunkId == enc->chunksCapacity) + return NULL; + + if (!Stream_EnsureRemainingCapacity(enc->pool, chunkSz)) + return NULL; + + ret = &enc->chunks[enc->freeChunkId]; + ret->poolOffset = Stream_GetPosition(enc->pool); + ret->capacity = chunkSz; + ret->used = commit ? chunkSz : 0; + if (id) + *id = enc->freeChunkId; + + enc->freeChunkId++; + Stream_Seek(enc->pool, chunkSz); + return ret; +} + +static WinPrAsn1EncContainer* asn1enc_get_free_container(WinPrAsn1Encoder* enc, size_t* id) +{ + WinPrAsn1EncContainer* ret = NULL; + WINPR_ASSERT(enc); + + if (enc->freeContainerIndex == enc->containerCapacity) + { + /* containers need a resize (or switch from static to dynamic) */ + WinPrAsn1EncContainer* src = + (enc->containers != &enc->staticContainers[0]) ? enc->containers : NULL; + WinPrAsn1EncContainer* tmp = realloc(src, (enc->containerCapacity + 10) * sizeof(*src)); + if (!tmp) + return NULL; + + if (enc->containers == &enc->staticContainers[0]) + memcpy(tmp, &enc->staticContainers[0], enc->containerCapacity * sizeof(*src)); + + enc->containers = tmp; + enc->containerCapacity += 10; + } + if (enc->freeContainerIndex == enc->containerCapacity) + return NULL; + + ret = &enc->containers[enc->freeContainerIndex]; + *id = enc->freeContainerIndex; + + enc->freeContainerIndex++; + return ret; +} + +static size_t lenBytes(size_t len) +{ + if (len < 128) + return 1; + if (len < (1 << 8)) + return 2; + if (len < (1 << 16)) + return 3; + if (len < (1 << 24)) + return 4; + + return 5; +} + +static void asn1WriteLen(wStream* s, size_t len) +{ + if (len < 128) + { + Stream_Write_UINT8(s, (UINT8)len); + } + else if (len < (1 << 8)) + { + Stream_Write_UINT8(s, 0x81); + Stream_Write_UINT8(s, (UINT8)len); + } + else if (len < (1 << 16)) + { + Stream_Write_UINT8(s, 0x82); + Stream_Write_UINT16_BE(s, (UINT16)len); + } + else if (len < (1 << 24)) + { + Stream_Write_UINT8(s, 0x83); + Stream_Write_UINT24_BE(s, (UINT32)len); + } + else + { + WINPR_ASSERT(len <= UINT32_MAX); + Stream_Write_UINT8(s, 0x84); + Stream_Write_UINT32_BE(s, (UINT32)len); + } +} + +static WinPrAsn1EncContainer* getAsn1Container(WinPrAsn1Encoder* enc, ContainerType ctype, + WinPrAsn1_tag tag, BOOL contextual, size_t maxLen) +{ + size_t ret = 0; + size_t chunkId = 0; + WinPrAsn1EncContainer* container = NULL; + + Asn1Chunk* chunk = asn1enc_get_free_chunk(enc, maxLen, FALSE, &chunkId); + if (!chunk) + return NULL; + + container = asn1enc_get_free_container(enc, &ret); + container->containerType = ctype; + container->tag = tag; + container->contextual = contextual; + container->headerChunkId = chunkId; + return container; +} + +BOOL WinPrAsn1EncAppContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId) +{ + WINPR_ASSERT_VALID_TAG(tagId); + return getAsn1Container(enc, ASN1_CONTAINER_APP, tagId, FALSE, 6) != NULL; +} + +BOOL WinPrAsn1EncSeqContainer(WinPrAsn1Encoder* enc) +{ + return getAsn1Container(enc, ASN1_CONTAINER_SEQ, 0, FALSE, 6) != NULL; +} + +BOOL WinPrAsn1EncSetContainer(WinPrAsn1Encoder* enc) +{ + return getAsn1Container(enc, ASN1_CONTAINER_SET, 0, FALSE, 6) != NULL; +} + +BOOL WinPrAsn1EncContextualSeqContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId) +{ + return getAsn1Container(enc, ASN1_CONTAINER_SEQ, tagId, TRUE, 6 + 6) != NULL; +} + +BOOL WinPrAsn1EncContextualSetContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId) +{ + return getAsn1Container(enc, ASN1_CONTAINER_SET, tagId, TRUE, 6 + 6) != NULL; +} + +BOOL WinPrAsn1EncContextualContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId) +{ + return getAsn1Container(enc, ASN1_CONTAINER_CONTEXT_ONLY, tagId, TRUE, 6) != NULL; +} + +BOOL WinPrAsn1EncOctetStringContainer(WinPrAsn1Encoder* enc) +{ + return getAsn1Container(enc, ASN1_CONTAINER_OCTETSTRING, 0, FALSE, 6) != NULL; +} + +BOOL WinPrAsn1EncContextualOctetStringContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId) +{ + return getAsn1Container(enc, ASN1_CONTAINER_OCTETSTRING, tagId, TRUE, 6 + 6) != NULL; +} + +size_t WinPrAsn1EncEndContainer(WinPrAsn1Encoder* enc) +{ + size_t innerLen = 0; + size_t unused = 0; + size_t innerHeaderBytes = 0; + size_t outerHeaderBytes = 0; + BYTE containerByte = 0; + WinPrAsn1EncContainer* container = NULL; + Asn1Chunk* chunk = NULL; + wStream staticS; + wStream* s = &staticS; + + WINPR_ASSERT(enc); + WINPR_ASSERT(enc->freeContainerIndex); + + /* compute inner length */ + container = &enc->containers[enc->freeContainerIndex - 1]; + innerLen = 0; + for (size_t i = container->headerChunkId + 1; i < enc->freeChunkId; i++) + innerLen += enc->chunks[i].used; + + /* compute effective headerLength */ + switch (container->containerType) + { + case ASN1_CONTAINER_SEQ: + containerByte = ER_TAG_SEQUENCE; + innerHeaderBytes = 1 + lenBytes(innerLen); + break; + case ASN1_CONTAINER_SET: + containerByte = ER_TAG_SET; + innerHeaderBytes = 1 + lenBytes(innerLen); + break; + case ASN1_CONTAINER_OCTETSTRING: + containerByte = ER_TAG_OCTET_STRING; + innerHeaderBytes = 1 + lenBytes(innerLen); + break; + case ASN1_CONTAINER_APP: + containerByte = ER_TAG_APP | container->tag; + innerHeaderBytes = 1 + lenBytes(innerLen); + break; + case ASN1_CONTAINER_CONTEXT_ONLY: + innerHeaderBytes = 0; + break; + default: + WLog_ERR(TAG, "invalid containerType"); + return 0; + } + + outerHeaderBytes = innerHeaderBytes; + if (container->contextual) + { + outerHeaderBytes = 1 + lenBytes(innerHeaderBytes + innerLen) + innerHeaderBytes; + } + + /* we write the headers at the end of the reserved space and we adjust + * the chunk to be a non reserved chunk */ + chunk = &enc->chunks[container->headerChunkId]; + unused = chunk->capacity - outerHeaderBytes; + chunk->poolOffset += unused; + chunk->capacity = chunk->used = outerHeaderBytes; + + Stream_StaticInit(s, Stream_Buffer(enc->pool) + chunk->poolOffset, outerHeaderBytes); + if (container->contextual) + { + Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | container->tag); + asn1WriteLen(s, innerHeaderBytes + innerLen); + } + + switch (container->containerType) + { + case ASN1_CONTAINER_SEQ: + case ASN1_CONTAINER_SET: + case ASN1_CONTAINER_OCTETSTRING: + case ASN1_CONTAINER_APP: + Stream_Write_UINT8(s, containerByte); + asn1WriteLen(s, innerLen); + break; + case ASN1_CONTAINER_CONTEXT_ONLY: + break; + default: + WLog_ERR(TAG, "invalid containerType"); + return 0; + } + + /* TODO: here there is place for packing chunks */ + enc->freeContainerIndex--; + return outerHeaderBytes + innerLen; +} + +static BOOL asn1_getWriteStream(WinPrAsn1Encoder* enc, size_t len, wStream* s) +{ + BYTE* dest = NULL; + Asn1Chunk* chunk = asn1enc_get_free_chunk(enc, len, TRUE, NULL); + if (!chunk) + return FALSE; + + dest = Stream_Buffer(enc->pool) + chunk->poolOffset + chunk->capacity - len; + Stream_StaticInit(s, dest, len); + return TRUE; +} + +size_t WinPrAsn1EncRawContent(WinPrAsn1Encoder* enc, const WinPrAsn1_MemoryChunk* c) +{ + wStream staticS; + wStream* s = &staticS; + + WINPR_ASSERT(enc); + WINPR_ASSERT(c); + + if (!asn1_getWriteStream(enc, c->len, s)) + return 0; + + Stream_Write(s, c->data, c->len); + return c->len; +} + +size_t WinPrAsn1EncContextualRawContent(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + const WinPrAsn1_MemoryChunk* c) +{ + wStream staticS; + wStream* s = &staticS; + + WINPR_ASSERT(enc); + WINPR_ASSERT(c); + WINPR_ASSERT_VALID_TAG(tagId); + + size_t len = 1 + lenBytes(c->len) + c->len; + if (!asn1_getWriteStream(enc, len, s)) + return 0; + + Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | tagId); + asn1WriteLen(s, c->len); + + Stream_Write(s, c->data, c->len); + return len; +} + +static size_t asn1IntegerLen(WinPrAsn1_INTEGER value) +{ + if (value <= 127 && value >= -128) + return 2; + else if (value <= 32767 && value >= -32768) + return 3; + else + return 5; +} + +static size_t WinPrAsn1EncIntegerLike(WinPrAsn1Encoder* enc, WinPrAsn1_tag b, + WinPrAsn1_INTEGER value) +{ + wStream staticS; + wStream* s = &staticS; + size_t len = 0; + + len = asn1IntegerLen(value); + if (!asn1_getWriteStream(enc, 1 + len, s)) + return 0; + + Stream_Write_UINT8(s, b); + switch (len) + { + case 2: + Stream_Write_UINT8(s, 1); + Stream_Write_UINT8(s, value); + break; + case 3: + Stream_Write_UINT8(s, 2); + Stream_Write_UINT16_BE(s, value); + break; + case 5: + Stream_Write_UINT8(s, 4); + Stream_Write_UINT32_BE(s, value); + break; + } + return 1 + len; +} + +size_t WinPrAsn1EncInteger(WinPrAsn1Encoder* enc, WinPrAsn1_INTEGER value) +{ + return WinPrAsn1EncIntegerLike(enc, ER_TAG_INTEGER, value); +} + +size_t WinPrAsn1EncEnumerated(WinPrAsn1Encoder* enc, WinPrAsn1_ENUMERATED value) +{ + return WinPrAsn1EncIntegerLike(enc, ER_TAG_ENUMERATED, value); +} + +static size_t WinPrAsn1EncContextualIntegerLike(WinPrAsn1Encoder* enc, WinPrAsn1_tag tag, + WinPrAsn1_tagId tagId, WinPrAsn1_INTEGER value) +{ + wStream staticS; + wStream* s = &staticS; + size_t len = 0; + size_t outLen = 0; + + WINPR_ASSERT(enc); + WINPR_ASSERT_VALID_TAG(tagId); + + len = asn1IntegerLen(value); + + outLen = 1 + lenBytes(1 + len) + (1 + len); + if (!asn1_getWriteStream(enc, outLen, s)) + return 0; + + Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | tagId); + asn1WriteLen(s, 1 + len); + + Stream_Write_UINT8(s, tag); + switch (len) + { + case 2: + Stream_Write_UINT8(s, 1); + Stream_Write_UINT8(s, value); + break; + case 3: + Stream_Write_UINT8(s, 2); + Stream_Write_UINT16_BE(s, value); + break; + case 5: + Stream_Write_UINT8(s, 4); + Stream_Write_UINT32_BE(s, value); + break; + } + return outLen; +} + +size_t WinPrAsn1EncContextualInteger(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + WinPrAsn1_INTEGER value) +{ + return WinPrAsn1EncContextualIntegerLike(enc, ER_TAG_INTEGER, tagId, value); +} + +size_t WinPrAsn1EncContextualEnumerated(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + WinPrAsn1_ENUMERATED value) +{ + return WinPrAsn1EncContextualIntegerLike(enc, ER_TAG_ENUMERATED, tagId, value); +} + +size_t WinPrAsn1EncBoolean(WinPrAsn1Encoder* enc, WinPrAsn1_BOOL b) +{ + wStream staticS; + wStream* s = &staticS; + + if (!asn1_getWriteStream(enc, 3, s)) + return 0; + + Stream_Write_UINT8(s, ER_TAG_BOOLEAN); + Stream_Write_UINT8(s, 1); + Stream_Write_UINT8(s, b ? 0xff : 0); + + return 3; +} + +size_t WinPrAsn1EncContextualBoolean(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, WinPrAsn1_BOOL b) +{ + wStream staticS; + wStream* s = &staticS; + + WINPR_ASSERT(enc); + WINPR_ASSERT_VALID_TAG(tagId); + + if (!asn1_getWriteStream(enc, 5, s)) + return 0; + + Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | tagId); + Stream_Write_UINT8(s, 3); + + Stream_Write_UINT8(s, ER_TAG_BOOLEAN); + Stream_Write_UINT8(s, 1); + Stream_Write_UINT8(s, b ? 0xff : 0); + + return 5; +} + +static size_t WinPrAsn1EncMemoryChunk(WinPrAsn1Encoder* enc, BYTE wireType, + const WinPrAsn1_MemoryChunk* mchunk) +{ + wStream s; + size_t len = 0; + + WINPR_ASSERT(enc); + WINPR_ASSERT(mchunk); + len = 1 + lenBytes(mchunk->len) + mchunk->len; + + if (!asn1_getWriteStream(enc, len, &s)) + return 0; + + Stream_Write_UINT8(&s, wireType); + asn1WriteLen(&s, mchunk->len); + Stream_Write(&s, mchunk->data, mchunk->len); + return len; +} + +size_t WinPrAsn1EncOID(WinPrAsn1Encoder* enc, const WinPrAsn1_OID* oid) +{ + return WinPrAsn1EncMemoryChunk(enc, ER_TAG_OBJECT_IDENTIFIER, oid); +} + +size_t WinPrAsn1EncOctetString(WinPrAsn1Encoder* enc, const WinPrAsn1_OctetString* octets) +{ + return WinPrAsn1EncMemoryChunk(enc, ER_TAG_OCTET_STRING, octets); +} + +size_t WinPrAsn1EncIA5String(WinPrAsn1Encoder* enc, WinPrAsn1_IA5STRING ia5) +{ + WinPrAsn1_MemoryChunk chunk; + WINPR_ASSERT(ia5); + chunk.data = (BYTE*)ia5; + chunk.len = strlen(ia5); + return WinPrAsn1EncMemoryChunk(enc, ER_TAG_IA5STRING, &chunk); +} + +size_t WinPrAsn1EncGeneralString(WinPrAsn1Encoder* enc, WinPrAsn1_STRING str) +{ + WinPrAsn1_MemoryChunk chunk; + WINPR_ASSERT(str); + chunk.data = (BYTE*)str; + chunk.len = strlen(str); + return WinPrAsn1EncMemoryChunk(enc, ER_TAG_GENERAL_STRING, &chunk); +} + +static size_t WinPrAsn1EncContextualMemoryChunk(WinPrAsn1Encoder* enc, BYTE wireType, + WinPrAsn1_tagId tagId, + const WinPrAsn1_MemoryChunk* mchunk) +{ + wStream s; + size_t len = 0; + size_t outLen = 0; + + WINPR_ASSERT(enc); + WINPR_ASSERT_VALID_TAG(tagId); + WINPR_ASSERT(mchunk); + len = 1 + lenBytes(mchunk->len) + mchunk->len; + + outLen = 1 + lenBytes(len) + len; + if (!asn1_getWriteStream(enc, outLen, &s)) + return 0; + + Stream_Write_UINT8(&s, ER_TAG_CONTEXTUAL | tagId); + asn1WriteLen(&s, len); + + Stream_Write_UINT8(&s, wireType); + asn1WriteLen(&s, mchunk->len); + Stream_Write(&s, mchunk->data, mchunk->len); + return outLen; +} + +size_t WinPrAsn1EncContextualOID(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + const WinPrAsn1_OID* oid) +{ + return WinPrAsn1EncContextualMemoryChunk(enc, ER_TAG_OBJECT_IDENTIFIER, tagId, oid); +} + +size_t WinPrAsn1EncContextualOctetString(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + const WinPrAsn1_OctetString* octets) +{ + return WinPrAsn1EncContextualMemoryChunk(enc, ER_TAG_OCTET_STRING, tagId, octets); +} + +size_t WinPrAsn1EncContextualIA5String(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + WinPrAsn1_IA5STRING ia5) +{ + WinPrAsn1_MemoryChunk chunk; + WINPR_ASSERT(ia5); + chunk.data = (BYTE*)ia5; + chunk.len = strlen(ia5); + + return WinPrAsn1EncContextualMemoryChunk(enc, ER_TAG_IA5STRING, tagId, &chunk); +} + +static void write2digit(wStream* s, UINT8 v) +{ + Stream_Write_UINT8(s, '0' + (v / 10)); + Stream_Write_UINT8(s, '0' + (v % 10)); +} + +size_t WinPrAsn1EncUtcTime(WinPrAsn1Encoder* enc, const WinPrAsn1_UTCTIME* utc) +{ + wStream staticS; + wStream* s = &staticS; + + WINPR_ASSERT(enc); + WINPR_ASSERT(utc); + WINPR_ASSERT(utc->year >= 2000); + + if (!asn1_getWriteStream(enc, 15, s)) + return 0; + + Stream_Write_UINT8(s, ER_TAG_UTCTIME); + Stream_Write_UINT8(s, 13); + + write2digit(s, utc->year - 2000); + write2digit(s, utc->month); + write2digit(s, utc->day); + write2digit(s, utc->hour); + write2digit(s, utc->minute); + write2digit(s, utc->second); + Stream_Write_UINT8(s, utc->tz); + return 15; +} + +size_t WinPrAsn1EncContextualUtcTime(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + const WinPrAsn1_UTCTIME* utc) +{ + wStream staticS; + wStream* s = &staticS; + + WINPR_ASSERT(enc); + WINPR_ASSERT_VALID_TAG(tagId); + WINPR_ASSERT(utc); + WINPR_ASSERT(utc->year >= 2000); + + if (!asn1_getWriteStream(enc, 17, s)) + return 0; + + Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | tagId); + Stream_Write_UINT8(s, 15); + + Stream_Write_UINT8(s, ER_TAG_UTCTIME); + Stream_Write_UINT8(s, 13); + + write2digit(s, utc->year - 2000); + write2digit(s, utc->month); + write2digit(s, utc->day); + write2digit(s, utc->hour); + write2digit(s, utc->minute); + write2digit(s, utc->second); + Stream_Write_UINT8(s, utc->tz); + + return 17; +} + +BOOL WinPrAsn1EncStreamSize(WinPrAsn1Encoder* enc, size_t* s) +{ + size_t finalSize = 0; + + WINPR_ASSERT(enc); + WINPR_ASSERT(s); + + if (enc->freeContainerIndex != 0) + { + WLog_ERR(TAG, "some container have not been closed"); + return FALSE; + } + + for (size_t i = 0; i < enc->freeChunkId; i++) + finalSize += enc->chunks[i].used; + *s = finalSize; + return TRUE; +} + +BOOL WinPrAsn1EncToStream(WinPrAsn1Encoder* enc, wStream* s) +{ + size_t finalSize = 0; + + WINPR_ASSERT(enc); + WINPR_ASSERT(s); + + if (!WinPrAsn1EncStreamSize(enc, &finalSize)) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(s, finalSize)) + return FALSE; + + for (size_t i = 0; i < enc->freeChunkId; i++) + { + BYTE* src = Stream_Buffer(enc->pool) + enc->chunks[i].poolOffset; + Stream_Write(s, src, enc->chunks[i].used); + } + + return TRUE; +} + +void WinPrAsn1Decoder_Init(WinPrAsn1Decoder* decoder, WinPrAsn1EncodingRule encoding, + wStream* source) +{ + WINPR_ASSERT(decoder); + WINPR_ASSERT(source); + + decoder->encoding = encoding; + memcpy(&decoder->source, source, sizeof(*source)); +} + +void WinPrAsn1Decoder_InitMem(WinPrAsn1Decoder* decoder, WinPrAsn1EncodingRule encoding, + const BYTE* source, size_t len) +{ + WINPR_ASSERT(decoder); + WINPR_ASSERT(source); + + decoder->encoding = encoding; + Stream_StaticConstInit(&decoder->source, source, len); +} + +BOOL WinPrAsn1DecPeekTag(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag) +{ + WINPR_ASSERT(dec); + WINPR_ASSERT(tag); + + if (Stream_GetRemainingLength(&dec->source) < 1) + return FALSE; + Stream_Peek(&dec->source, tag, 1); + return TRUE; +} + +static size_t readLen(wStream* s, size_t* len, BOOL derCheck) +{ + size_t retLen = 0; + size_t ret = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return 0; + + Stream_Read_UINT8(s, retLen); + ret++; + if (retLen & 0x80) + { + BYTE tmp = 0; + size_t nBytes = (retLen & 0x7f); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, nBytes)) + return 0; + + ret += nBytes; + for (retLen = 0; nBytes; nBytes--) + { + Stream_Read_UINT8(s, tmp); + retLen = (retLen << 8) + tmp; + } + + if (derCheck) + { + /* check that the DER rule is respected, and that length encoding is optimal */ + if (ret > 1 && retLen < 128) + return 0; + } + } + + *len = retLen; + return ret; +} + +static size_t readTagAndLen(WinPrAsn1Decoder* dec, wStream* s, WinPrAsn1_tag* tag, size_t* len) +{ + size_t lenBytes = 0; + + if (Stream_GetRemainingLength(s) < 1) + return 0; + + Stream_Read(s, tag, 1); + lenBytes = readLen(s, len, (dec->encoding == WINPR_ASN1_DER)); + if (lenBytes == 0) + return 0; + + return 1 + lenBytes; +} + +size_t WinPrAsn1DecReadTagAndLen(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, size_t* len) +{ + WINPR_ASSERT(dec); + WINPR_ASSERT(tag); + WINPR_ASSERT(len); + + return readTagAndLen(dec, &dec->source, tag, len); +} + +size_t WinPrAsn1DecPeekTagAndLen(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, size_t* len) +{ + wStream staticS; + wStream* s = &staticS; + + WINPR_ASSERT(dec); + + Stream_StaticConstInit(s, Stream_ConstPointer(&dec->source), + Stream_GetRemainingLength(&dec->source)); + return readTagAndLen(dec, s, tag, len); +} + +size_t WinPrAsn1DecReadTagLenValue(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, size_t* len, + WinPrAsn1Decoder* value) +{ + size_t ret = 0; + WINPR_ASSERT(dec); + WINPR_ASSERT(tag); + WINPR_ASSERT(len); + WINPR_ASSERT(value); + + ret = readTagAndLen(dec, &dec->source, tag, len); + if (!ret) + return 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, *len)) + return 0; + + value->encoding = dec->encoding; + Stream_StaticInit(&value->source, Stream_Pointer(&dec->source), *len); + Stream_Seek(&dec->source, *len); + return ret + *len; +} + +size_t WinPrAsn1DecReadBoolean(WinPrAsn1Decoder* dec, WinPrAsn1_BOOL* target) +{ + BYTE v = 0; + WinPrAsn1_tag tag = 0; + size_t len = 0; + size_t ret = 0; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != ER_TAG_BOOLEAN) + return 0; + if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || len != 1) + return 0; + + Stream_Read_UINT8(&dec->source, v); + *target = !!v; + return ret; +} + +static size_t WinPrAsn1DecReadIntegerLike(WinPrAsn1Decoder* dec, WinPrAsn1_tag expectedTag, + WinPrAsn1_INTEGER* target) +{ + signed char v = 0; + WinPrAsn1_tag tag = 0; + size_t len = 0; + size_t ret = 0; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != expectedTag) + return 0; + if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || len > 4) + return 0; + + ret += len; + for (*target = 0; len; len--) + { + Stream_Read_INT8(&dec->source, v); + *target = (*target << 8) + v; + } + + /* TODO: check ber/der rules */ + return ret; +} + +size_t WinPrAsn1DecReadInteger(WinPrAsn1Decoder* dec, WinPrAsn1_INTEGER* target) +{ + return WinPrAsn1DecReadIntegerLike(dec, ER_TAG_INTEGER, target); +} + +size_t WinPrAsn1DecReadEnumerated(WinPrAsn1Decoder* dec, WinPrAsn1_ENUMERATED* target) +{ + return WinPrAsn1DecReadIntegerLike(dec, ER_TAG_ENUMERATED, target); +} + +static size_t WinPrAsn1DecReadMemoryChunkLike(WinPrAsn1Decoder* dec, WinPrAsn1_tag expectedTag, + WinPrAsn1_MemoryChunk* target, BOOL allocate) +{ + WinPrAsn1_tag tag = 0; + size_t len = 0; + size_t ret = 0; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != expectedTag) + return 0; + if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len)) + return 0; + + ret += len; + + target->len = len; + if (allocate) + { + target->data = malloc(len); + if (!target->data) + return 0; + Stream_Read(&dec->source, target->data, len); + } + else + { + target->data = Stream_Pointer(&dec->source); + Stream_Seek(&dec->source, len); + } + + return ret; +} + +size_t WinPrAsn1DecReadOID(WinPrAsn1Decoder* dec, WinPrAsn1_OID* target, BOOL allocate) +{ + return WinPrAsn1DecReadMemoryChunkLike(dec, ER_TAG_OBJECT_IDENTIFIER, + (WinPrAsn1_MemoryChunk*)target, allocate); +} + +size_t WinPrAsn1DecReadOctetString(WinPrAsn1Decoder* dec, WinPrAsn1_OctetString* target, + BOOL allocate) +{ + return WinPrAsn1DecReadMemoryChunkLike(dec, ER_TAG_OCTET_STRING, (WinPrAsn1_OctetString*)target, + allocate); +} + +size_t WinPrAsn1DecReadIA5String(WinPrAsn1Decoder* dec, WinPrAsn1_IA5STRING* target) +{ + WinPrAsn1_tag tag = 0; + size_t len = 0; + size_t ret = 0; + WinPrAsn1_IA5STRING s = NULL; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != ER_TAG_IA5STRING) + return 0; + if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len)) + return 0; + + ret += len; + + s = malloc(len + 1); + if (!s) + return 0; + Stream_Read(&dec->source, s, len); + s[len] = 0; + *target = s; + return ret; +} + +size_t WinPrAsn1DecReadGeneralString(WinPrAsn1Decoder* dec, WinPrAsn1_STRING* target) +{ + WinPrAsn1_tag tag = 0; + size_t len = 0; + size_t ret = 0; + WinPrAsn1_IA5STRING s = NULL; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != ER_TAG_GENERAL_STRING) + return 0; + if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len)) + return 0; + + ret += len; + + s = malloc(len + 1); + if (!s) + return 0; + Stream_Read(&dec->source, s, len); + s[len] = 0; + *target = s; + return ret; +} + +static int read2digits(wStream* s) +{ + int ret = 0; + char c = 0; + + Stream_Read_UINT8(s, c); + if (c < '0' || c > '9') + return -1; + + ret = (c - '0') * 10; + + Stream_Read_UINT8(s, c); + if (c < '0' || c > '9') + return -1; + + ret += (c - '0'); + return ret; +} + +size_t WinPrAsn1DecReadUtcTime(WinPrAsn1Decoder* dec, WinPrAsn1_UTCTIME* target) +{ + WinPrAsn1_tag tag = 0; + size_t len = 0; + size_t ret = 0; + int v = 0; + wStream sub; + wStream* s = ⊂ + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != ER_TAG_UTCTIME) + return 0; + if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || len < 12) + return 0; + + Stream_StaticConstInit(s, Stream_ConstPointer(&dec->source), len); + + v = read2digits(s); + if (v <= 0) + return 0; + target->year = 2000 + v; + + v = read2digits(s); + if (v <= 0) + return 0; + target->month = v; + + v = read2digits(s); + if (v <= 0) + return 0; + target->day = v; + + v = read2digits(s); + if (v <= 0) + return 0; + target->hour = v; + + v = read2digits(s); + if (v <= 0) + return 0; + target->minute = v; + + v = read2digits(s); + if (v <= 0) + return 0; + target->second = v; + + if (Stream_GetRemainingLength(s) >= 1) + { + Stream_Read_UINT8(s, target->tz); + } + + Stream_Seek(&dec->source, len); + ret += len; + + return ret; +} + +size_t WinPrAsn1DecReadNull(WinPrAsn1Decoder* dec) +{ + WinPrAsn1_tag tag = 0; + size_t len = 0; + size_t ret = 0; + + WINPR_ASSERT(dec); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != ER_TAG_NULL || len) + return 0; + + return ret; +} + +static size_t readConstructed(WinPrAsn1Decoder* dec, wStream* s, WinPrAsn1_tag* tag, + WinPrAsn1Decoder* target) +{ + size_t len = 0; + size_t ret = 0; + + ret = readTagAndLen(dec, s, tag, &len); + if (!ret || !Stream_CheckAndLogRequiredLength(TAG, s, len)) + return 0; + + target->encoding = dec->encoding; + Stream_StaticConstInit(&target->source, Stream_ConstPointer(s), len); + Stream_Seek(s, len); + return ret + len; +} + +size_t WinPrAsn1DecReadApp(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId, WinPrAsn1Decoder* target) +{ + WinPrAsn1_tag tag = 0; + size_t ret = 0; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readConstructed(dec, &dec->source, &tag, target); + if ((tag & ER_TAG_APP) != ER_TAG_APP) + return 0; + + *tagId = (tag & ER_TAG_MASK); + return ret; +} + +size_t WinPrAsn1DecReadSequence(WinPrAsn1Decoder* dec, WinPrAsn1Decoder* target) +{ + WinPrAsn1_tag tag = 0; + size_t ret = 0; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readConstructed(dec, &dec->source, &tag, target); + if (tag != ER_TAG_SEQUENCE) + return 0; + + return ret; +} + +size_t WinPrAsn1DecReadSet(WinPrAsn1Decoder* dec, WinPrAsn1Decoder* target) +{ + WinPrAsn1_tag tag = 0; + size_t ret = 0; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readConstructed(dec, &dec->source, &tag, target); + if (tag != ER_TAG_SET) + return 0; + + return ret; +} + +static size_t readContextualTag(WinPrAsn1Decoder* dec, wStream* s, WinPrAsn1_tagId* tagId, + WinPrAsn1Decoder* ctxtDec) +{ + size_t ret = 0; + WinPrAsn1_tag ftag = 0; + + ret = readConstructed(dec, s, &ftag, ctxtDec); + if (!ret) + return 0; + + if ((ftag & ER_TAG_CONTEXTUAL) != ER_TAG_CONTEXTUAL) + return 0; + + *tagId = (ftag & ER_TAG_MASK); + return ret; +} + +size_t WinPrAsn1DecReadContextualTag(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId, + WinPrAsn1Decoder* ctxtDec) +{ + WINPR_ASSERT(dec); + WINPR_ASSERT(tagId); + WINPR_ASSERT(ctxtDec); + + return readContextualTag(dec, &dec->source, tagId, ctxtDec); +} + +size_t WinPrAsn1DecPeekContextualTag(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId, + WinPrAsn1Decoder* ctxtDec) +{ + wStream staticS; + WINPR_ASSERT(dec); + + Stream_StaticConstInit(&staticS, Stream_ConstPointer(&dec->source), + Stream_GetRemainingLength(&dec->source)); + return readContextualTag(dec, &staticS, tagId, ctxtDec); +} + +static size_t readContextualHeader(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1Decoder* content) +{ + WinPrAsn1_tag ftag = 0; + size_t ret = 0; + + WINPR_ASSERT(dec); + WINPR_ASSERT(error); + WINPR_ASSERT(content); + + *error = TRUE; + ret = WinPrAsn1DecPeekContextualTag(dec, &ftag, content); + if (!ret) + return 0; + + if (ftag != tagId) + { + *error = FALSE; + return 0; + } + + *error = FALSE; + return ret; +} + +size_t WinPrAsn1DecReadContextualBool(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1_BOOL* target) +{ + size_t ret = 0; + size_t ret2 = 0; + WinPrAsn1Decoder content; + + ret = readContextualHeader(dec, tagId, error, &content); + if (!ret) + return 0; + + ret2 = WinPrAsn1DecReadBoolean(&content, target); + if (!ret2) + { + *error = TRUE; + return 0; + } + + Stream_Seek(&dec->source, ret); + return ret; +} + +size_t WinPrAsn1DecReadContextualInteger(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1_INTEGER* target) +{ + size_t ret = 0; + size_t ret2 = 0; + WinPrAsn1Decoder content; + + ret = readContextualHeader(dec, tagId, error, &content); + if (!ret) + return 0; + + ret2 = WinPrAsn1DecReadInteger(&content, target); + if (!ret2) + { + *error = TRUE; + return 0; + } + + Stream_Seek(&dec->source, ret); + return ret; +} + +size_t WinPrAsn1DecReadContextualOID(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1_OID* target, BOOL allocate) +{ + size_t ret = 0; + size_t ret2 = 0; + WinPrAsn1Decoder content; + + ret = readContextualHeader(dec, tagId, error, &content); + if (!ret) + return 0; + + ret2 = WinPrAsn1DecReadOID(&content, target, allocate); + if (!ret2) + { + *error = TRUE; + return 0; + } + + Stream_Seek(&dec->source, ret); + return ret; +} + +size_t WinPrAsn1DecReadContextualOctetString(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, + BOOL* error, WinPrAsn1_OctetString* target, + BOOL allocate) +{ + size_t ret = 0; + size_t ret2 = 0; + WinPrAsn1Decoder content; + + ret = readContextualHeader(dec, tagId, error, &content); + if (!ret) + return 0; + + ret2 = WinPrAsn1DecReadOctetString(&content, target, allocate); + if (!ret2) + { + *error = TRUE; + return 0; + } + + Stream_Seek(&dec->source, ret); + return ret; +} + +size_t WinPrAsn1DecReadContextualSequence(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1Decoder* target) +{ + size_t ret = 0; + size_t ret2 = 0; + WinPrAsn1Decoder content; + + ret = readContextualHeader(dec, tagId, error, &content); + if (!ret) + return 0; + + ret2 = WinPrAsn1DecReadSequence(&content, target); + if (!ret2) + { + *error = TRUE; + return 0; + } + + Stream_Seek(&dec->source, ret); + return ret; +} + +wStream WinPrAsn1DecGetStream(WinPrAsn1Decoder* dec) +{ + wStream s = { 0 }; + WINPR_ASSERT(dec); + + Stream_StaticConstInit(&s, Stream_ConstPointer(&dec->source), + Stream_GetRemainingLength(&dec->source)); + return s; +} |