diff --git a/include/freerdp/crypto/er.h b/include/freerdp/crypto/er.h index 43cb8af55..d981b4e93 100644 --- a/include/freerdp/crypto/er.h +++ b/include/freerdp/crypto/er.h @@ -46,7 +46,7 @@ #define ER_TAG_INTEGER 0x02 #define ER_TAG_BIT_STRING 0x03 #define ER_TAG_OCTET_STRING 0x04 -#define ER_TAG_OBJECT_IDENFIER 0x06 +#define ER_TAG_OBJECT_IDENTIFIER 0x06 #define ER_TAG_ENUMERATED 0x0A #define ER_TAG_SEQUENCE 0x10 #define ER_TAG_SEQUENCE_OF 0x10 diff --git a/winpr/include/winpr/asn1.h b/winpr/include/winpr/asn1.h new file mode 100644 index 000000000..2583383e8 --- /dev/null +++ b/winpr/include/winpr/asn1.h @@ -0,0 +1,188 @@ +/** + * WinPR: Windows Portable Runtime + * ASN1 encoder / decoder + * + * Copyright 2022 David Fort + * + * 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. + */ + +#ifndef WINPR_ASN1_H_ +#define WINPR_ASN1_H_ + +#include +#include +#include + +#define ER_TAG_MASK 0x1F + +enum +{ + ER_TAG_BOOLEAN = 0x01, + ER_TAG_INTEGER = 0x02, + ER_TAG_BIT_STRING = 0x03, + ER_TAG_OCTET_STRING = 0x04, + ER_TAG_NULL = 0x05, + ER_TAG_OBJECT_IDENTIFIER = 0x06, + ER_TAG_ENUMERATED = 0x0A, + ER_TAG_UTF8STRING = 0x0C, + ER_TAG_PRINTABLE_STRING = 0x13, + ER_TAG_IA5STRING = 0x16, + ER_TAG_UTCTIME = 0x17, + ER_TAG_GENERAL_STRING = 0x1B, + ER_TAG_GENERALIZED_TIME = 0x18, + + ER_TAG_APP = 0x60, + ER_TAG_SEQUENCE = 0x30, + ER_TAG_SEQUENCE_OF = 0x30, + ER_TAG_SET = 0x31, + ER_TAG_SET_OF = 0x31, + + ER_TAG_CONTEXTUAL = 0xA0 +}; + +/** @brief rules for encoding */ +typedef enum +{ + WINPR_ASN1_BER, + WINPR_ASN1_DER +} WinPrAsn1EncodingRule; + +typedef struct WinPrAsn1Encoder WinPrAsn1Encoder; + +struct WinPrAsn1Decoder +{ + WinPrAsn1EncodingRule encoding; + wStream source; +}; + +typedef struct WinPrAsn1Decoder WinPrAsn1Decoder; + +typedef BYTE WinPrAsn1_tag; +typedef BYTE WinPrAsn1_tagId; +typedef BOOL WinPrAsn1_BOOL; +typedef INT32 WinPrAsn1_INTEGER; +typedef char* WinPrAsn1_STRING; +typedef char* WinPrAsn1_IA5STRING; +typedef struct +{ + size_t len; + BYTE* data; +} WinPrAsn1_MemoryChunk; + +typedef WinPrAsn1_MemoryChunk WinPrAsn1_OID; +typedef WinPrAsn1_MemoryChunk WinPrAsn1_OctetString; + +typedef struct +{ + UINT16 year; + UINT8 month; + UINT8 day; + UINT8 hour; + UINT8 minute; + UINT8 second; + char tz; +} WinPrAsn1_UTCTIME; + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + WINPR_API void WinPrAsn1FreeOID(WinPrAsn1_OID* poid); + WINPR_API void WinPrAsn1FreeOctetString(WinPrAsn1_OctetString* octets); + + /* decoder functions */ + + WINPR_API void WinPrAsn1Decoder_Init(WinPrAsn1Decoder* dec, WinPrAsn1EncodingRule encoding, + wStream* source); + + WINPR_API BOOL WinPrAsn1DecPeekTag(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag); + WINPR_API size_t WinPrAsn1DecReadTagAndLen(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, + size_t* len); + WINPR_API size_t WinPrAsn1DecPeekTagAndLen(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, + size_t* len); + WINPR_API size_t WinPrAsn1DecReadTagLenValue(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, + size_t* len, WinPrAsn1Decoder* value); + WINPR_API size_t WinPrAsn1DecReadBoolean(WinPrAsn1Decoder* dec, WinPrAsn1_BOOL* target); + WINPR_API size_t WinPrAsn1DecReadInteger(WinPrAsn1Decoder* dec, WinPrAsn1_INTEGER* target); + WINPR_API size_t WinPrAsn1DecReadOID(WinPrAsn1Decoder* dec, WinPrAsn1_OID* target); + WINPR_API size_t WinPrAsn1DecReadOctetString(WinPrAsn1Decoder* dec, + WinPrAsn1_OctetString* target); + WINPR_API size_t WinPrAsn1DecReadIA5String(WinPrAsn1Decoder* dec, WinPrAsn1_IA5STRING* target); + WINPR_API size_t WinPrAsn1DecReadUtcTime(WinPrAsn1Decoder* dec, WinPrAsn1_UTCTIME* target); + WINPR_API size_t WinPrAsn1DecReadNull(WinPrAsn1Decoder* dec); + + WINPR_API size_t WinPrAsn1DecReadApp(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId, + WinPrAsn1Decoder* setDec); + WINPR_API size_t WinPrAsn1DecReadSequence(WinPrAsn1Decoder* dec, WinPrAsn1Decoder* seqDec); + WINPR_API size_t WinPrAsn1DecReadSet(WinPrAsn1Decoder* dec, WinPrAsn1Decoder* setDec); + + WINPR_API size_t WinPrAsn1DecReadContextualTag(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId, + WinPrAsn1Decoder* ctxtDec); + WINPR_API size_t WinPrAsn1DecPeekContextualTag(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId, + WinPrAsn1Decoder* ctxtDec); + + WINPR_API size_t WinPrAsn1DecReadContextualBool(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, + BOOL* error, WinPrAsn1_BOOL* target); + WINPR_API size_t WinPrAsn1DecReadContextualInteger(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, + BOOL* error, WinPrAsn1_INTEGER* target); + WINPR_API size_t WinPrAsn1DecReadContextualOID(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, + BOOL* error, WinPrAsn1_OID* target); + WINPR_API size_t WinPrAsn1DecReadContextualSequence(WinPrAsn1Decoder* dec, + WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1Decoder* target); + + /* encoder functions */ + + WINPR_API WinPrAsn1Encoder* WinPrAsn1Encoder_New(WinPrAsn1EncodingRule encoding); + WINPR_API void WinPrAsn1Encoder_Reset(WinPrAsn1Encoder* enc); + + WINPR_API BOOL WinPrAsn1EncAppContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId); + WINPR_API BOOL WinPrAsn1EncSeqContainer(WinPrAsn1Encoder* enc); + WINPR_API BOOL WinPrAsn1EncContextualSeqContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId); + WINPR_API BOOL WinPrAsn1EncSetContainer(WinPrAsn1Encoder* enc); + WINPR_API BOOL WinPrAsn1EncContextualSetContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId); + WINPR_API BOOL WinPrAsn1EncContextualContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId); + WINPR_API size_t WinPrAsn1EncEndContainer(WinPrAsn1Encoder* enc); + + WINPR_API size_t WinPrAsn1EncInteger(WinPrAsn1Encoder* enc, WinPrAsn1_INTEGER integer); + WINPR_API size_t WinPrAsn1EncContextualInteger(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + WinPrAsn1_INTEGER integer); + WINPR_API size_t WinPrAsn1EncBoolean(WinPrAsn1Encoder* enc, WinPrAsn1_BOOL b); + WINPR_API size_t WinPrAsn1EncContextualBoolean(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + WinPrAsn1_BOOL b); + WINPR_API size_t WinPrAsn1EncOID(WinPrAsn1Encoder* enc, const WinPrAsn1_OID* oid); + WINPR_API size_t WinPrAsn1EncContextualOID(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + const WinPrAsn1_OID* oid); + WINPR_API size_t WinPrAsn1EncOctetString(WinPrAsn1Encoder* enc, + const WinPrAsn1_OctetString* oid); + WINPR_API size_t WinPrAsn1EncContextualOctetString(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + const WinPrAsn1_OctetString* oid); + WINPR_API size_t WinPrAsn1EncIA5String(WinPrAsn1Encoder* enc, WinPrAsn1_IA5STRING ia5); + WINPR_API size_t WinPrAsn1EncContextualIA5String(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + WinPrAsn1_IA5STRING ia5); + WINPR_API size_t WinPrAsn1EncUtcTime(WinPrAsn1Encoder* enc, const WinPrAsn1_UTCTIME* utc); + WINPR_API size_t WinPrAsn1EncContextualUtcTime(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + const WinPrAsn1_UTCTIME* utc); + + WINPR_API BOOL WinPrAsn1EncStreamSize(WinPrAsn1Encoder* enc, size_t* s); + WINPR_API BOOL WinPrAsn1EncToStream(WinPrAsn1Encoder* enc, wStream* s); + + WINPR_API void WinPrAsn1Encoder_Free(WinPrAsn1Encoder** penc); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* WINPR_ASN1_H_ */ diff --git a/winpr/include/winpr/stream.h b/winpr/include/winpr/stream.h index 0e566e22b..ab6d098bd 100644 --- a/winpr/include/winpr/stream.h +++ b/winpr/include/winpr/stream.h @@ -287,6 +287,15 @@ extern "C" *_s->pointer++ = (_v)&0xFF; } + static INLINE void Stream_Write_UINT24_BE(wStream* _s, UINT32 _v) + { + WINPR_ASSERT(_s); + WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 3); + *_s->pointer++ = ((_v) >> 16) & 0xFF; + *_s->pointer++ = ((_v) >> 8) & 0xFF; + *_s->pointer++ = (_v)&0xFF; + } + static INLINE void Stream_Write_INT32(wStream* _s, INT32 _v) { WINPR_ASSERT(_s); diff --git a/winpr/libwinpr/utils/CMakeLists.txt b/winpr/libwinpr/utils/CMakeLists.txt index 7d984efe5..1c8b42b17 100644 --- a/winpr/libwinpr/utils/CMakeLists.txt +++ b/winpr/libwinpr/utils/CMakeLists.txt @@ -87,6 +87,9 @@ set(WLOG_SRCS ${JOURNALD_SRCS} ) +set(ASN1_SRCS + asn1/asn1.c +) set(SRCS ini.c @@ -141,7 +144,9 @@ endif() winpr_module_add(${SRCS} ${COLLECTIONS_SRCS} ${LODEPNG_SRCS} - ${WLOG_SRCS}) + ${WLOG_SRCS} + ${ASN1_SRCS} +) winpr_include_directory_add( "lodepng" diff --git a/winpr/libwinpr/utils/asn1/asn1.c b/winpr/libwinpr/utils/asn1/asn1.c new file mode 100644 index 000000000..1cb3c2ae9 --- /dev/null +++ b/winpr/libwinpr/utils/asn1/asn1.c @@ -0,0 +1,1297 @@ +/** + * WinPR: Windows Portable Runtime + * ASN1 routines + * + * Copyright 2022 David Fort + * + * 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 + +#include +#include +#include + +#define TAG "winpr.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 +} 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; + + 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; + 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, src, 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; + 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, src, 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, len); + } + else if (len < (1 << 8)) + { + Stream_Write_UINT8(s, 0x81); + Stream_Write_UINT8(s, len); + } + else if (len < (1 << 16)) + { + Stream_Write_UINT8(s, 0x82); + Stream_Write_UINT16_BE(s, len); + } + else if (len < (1 << 24)) + { + Stream_Write_UINT8(s, 0x83); + Stream_Write_UINT24_BE(s, len); + } + else + { + Stream_Write_UINT8(s, 0x84); + Stream_Write_UINT32_BE(s, len); + } +} + +static WinPrAsn1EncContainer* getAsn1Container(WinPrAsn1Encoder* enc, ContainerType ctype, + WinPrAsn1_tag tag, BOOL contextual, size_t maxLen) +{ + size_t ret; + size_t chunkId; + WinPrAsn1EncContainer* container; + + 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; +} + +size_t WinPrAsn1EncEndContainer(WinPrAsn1Encoder* enc) +{ + size_t innerLen, i, unused; + size_t innerHeaderBytes, outerHeaderBytes; + BYTE containerByte; + WinPrAsn1EncContainer* container; + Asn1Chunk* chunk; + wStream staticS; + wStream* s = &staticS; + + WINPR_ASSERT(enc); + WINPR_ASSERT(enc->freeContainerIndex); + + /* compute inner length */ + container = &enc->containers[enc->freeContainerIndex - 1]; + innerLen = 0; + for (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_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_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; + 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; +} + +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; +} + +size_t WinPrAsn1EncInteger(WinPrAsn1Encoder* enc, WinPrAsn1_INTEGER value) +{ + wStream staticS; + wStream* s = &staticS; + size_t len; + + len = asn1IntegerLen(value); + if (!asn1_getWriteStream(enc, 1 + len, s)) + return 0; + + Stream_Write_UINT8(s, ER_TAG_INTEGER); + 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 WinPrAsn1EncContextualInteger(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, + WinPrAsn1_INTEGER value) +{ + wStream staticS; + wStream* s = &staticS; + size_t len, outLen; + + 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, ER_TAG_INTEGER); + 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 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; + + 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 WinPrAsn1EncContextualMemoryChunk(WinPrAsn1Encoder* enc, BYTE wireType, + WinPrAsn1_tagId tagId, const WinPrAsn1_MemoryChunk* mchunk) +{ + wStream s; + size_t len, outLen; + + 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, ER_TAG_OBJECT_IDENTIFIER); + 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_OBJECT_IDENTIFIER, 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; + size_t i; + + WINPR_ASSERT(enc); + WINPR_ASSERT(s); + + if (enc->freeContainerIndex != 0) + { + WLog_ERR(TAG, "some container have not been closed"); + return FALSE; + } + + for (i = 0; i < enc->freeChunkId; i++) + finalSize += enc->chunks[i].used; + *s = finalSize; + return TRUE; +} + +BOOL WinPrAsn1EncToStream(WinPrAsn1Encoder* enc, wStream* s) +{ + size_t finalSize; + size_t i; + + WINPR_ASSERT(enc); + WINPR_ASSERT(s); + + if (!WinPrAsn1EncStreamSize(enc, &finalSize)) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(s, finalSize)) + return FALSE; + + for (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(source); + + decoder->encoding = encoding; + memcpy(&decoder->source, source, sizeof(*source)); +} + +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; + size_t ret = 0; + + if (Stream_GetRemainingLength(s) < 1) + return 0; + + Stream_Read_UINT8(s, retLen); + ret++; + if (retLen & 0x80) + { + BYTE tmp; + size_t nBytes = (retLen & 0x80); + + if (Stream_GetRemainingLength(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; + + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + + Stream_Read(s, tag, 1); + lenBytes = readLen(s, len, (dec->encoding == WINPR_ASN1_DER)); + if (!lenBytes) + 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; + 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_GetRemainingLength(&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; + WinPrAsn1_tag tag; + size_t len; + size_t ret; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != ER_TAG_BOOLEAN) + return 0; + if (Stream_GetRemainingLength(&dec->source) < len || len != 1) + return 0; + + Stream_Read_UINT8(&dec->source, v); + *target = !!v; + return ret; +} + +size_t WinPrAsn1DecReadInteger(WinPrAsn1Decoder* dec, WinPrAsn1_INTEGER* target) +{ + signed char v; + WinPrAsn1_tag tag; + size_t len; + size_t ret; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != ER_TAG_INTEGER) + return 0; + if (Stream_GetRemainingLength(&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; +} + +static size_t WinPrAsn1DecReadMemoryChunkLike(WinPrAsn1Decoder* dec, WinPrAsn1_tag expectedTag, + WinPrAsn1_MemoryChunk* target) +{ + WinPrAsn1_tag tag; + size_t len; + size_t ret; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != expectedTag) + return 0; + if (Stream_GetRemainingLength(&dec->source) < len) + return 0; + + ret += len; + + target->len = len; + target->data = malloc(len); + if (!target->data) + return 0; + Stream_Read(&dec->source, target->data, len); + + return ret; +} + +size_t WinPrAsn1DecReadOID(WinPrAsn1Decoder* dec, WinPrAsn1_OID* target) +{ + return WinPrAsn1DecReadMemoryChunkLike(dec, ER_TAG_OBJECT_IDENTIFIER, + (WinPrAsn1_MemoryChunk*)target); +} + +size_t WinPrAsn1DecReadOctetString(WinPrAsn1Decoder* dec, WinPrAsn1_OctetString* target) +{ + return WinPrAsn1DecReadMemoryChunkLike(dec, ER_TAG_OCTET_STRING, + (WinPrAsn1_OctetString*)target); +} + +size_t WinPrAsn1DecReadIA5String(WinPrAsn1Decoder* dec, WinPrAsn1_IA5STRING* target) +{ + WinPrAsn1_tag tag; + size_t len; + size_t ret; + WinPrAsn1_IA5STRING s; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readTagAndLen(dec, &dec->source, &tag, &len); + if (!ret || tag != ER_TAG_IA5STRING) + return 0; + if (Stream_GetRemainingLength(&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; + + 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; + size_t len; + size_t ret; + int v; + 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_GetRemainingLength(&dec->source) < len || len < 12) + return 0; + + Stream_StaticConstInit(s, Stream_Pointer(&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; + size_t len; + size_t ret; + + 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; + size_t ret; + + ret = readTagAndLen(dec, s, tag, &len); + if (!ret || Stream_GetRemainingLength(s) < len) + return 0; + + target->encoding = dec->encoding; + Stream_StaticConstInit(&target->source, Stream_Pointer(s), len); + Stream_Seek(s, len); + return ret + len; +} + +size_t WinPrAsn1DecReadApp(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId, WinPrAsn1Decoder* target) +{ + WinPrAsn1_tag tag; + size_t ret; + + 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; + size_t ret; + + 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; + size_t ret; + + WINPR_ASSERT(dec); + WINPR_ASSERT(target); + + ret = readConstructed(dec, &dec->source, &tag, target); + if (tag != ER_TAG_SET) + return 0; + + return ret; +} + +size_t readContextualTag(WinPrAsn1Decoder* dec, wStream* s, WinPrAsn1_tagId* tagId, + WinPrAsn1Decoder* ctxtDec) +{ + size_t ret; + WinPrAsn1_tag ftag; + + 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_Pointer(&dec->source), + Stream_GetRemainingLength(&dec->source)); + return readContextualTag(dec, &staticS, tagId, ctxtDec); +} + +size_t WinPrAsn1DecReadContextualBool(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1_BOOL* target) +{ + size_t ret, ret2; + WinPrAsn1_tag ftag; + WinPrAsn1Decoder content; + + WINPR_ASSERT(error); + + ret = WinPrAsn1DecPeekContextualTag(dec, &ftag, &content); + if (!ret || ftag != tagId) + return 0; + + ret2 = WinPrAsn1DecReadBoolean(&content, target); + if (!ret2) + { + *error = TRUE; + return 0; + } + + *error = FALSE; + Stream_Seek(&dec->source, ret); + return ret; +} + +size_t WinPrAsn1DecReadContextualInteger(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1_INTEGER* target) +{ + size_t ret, ret2; + WinPrAsn1_tag ftag; + WinPrAsn1Decoder content; + + WINPR_ASSERT(error); + + ret = WinPrAsn1DecPeekContextualTag(dec, &ftag, &content); + if (!ret || ftag != tagId) + return 0; + + ret2 = WinPrAsn1DecReadInteger(&content, target); + if (!ret2) + { + *error = TRUE; + return 0; + } + + *error = FALSE; + Stream_Seek(&dec->source, ret); + return ret; +} + +size_t WinPrAsn1DecReadContextualOID(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1_OID* target) +{ + size_t ret, ret2; + WinPrAsn1_tag ftag; + WinPrAsn1Decoder content; + + WINPR_ASSERT(error); + + ret = WinPrAsn1DecPeekContextualTag(dec, &ftag, &content); + if (!ret || ftag != tagId) + return 0; + + ret2 = WinPrAsn1DecReadOID(&content, target); + if (!ret2) + { + *error = TRUE; + return 0; + } + + *error = FALSE; + Stream_Seek(&dec->source, ret); + return ret; +} + +size_t WinPrAsn1DecReadContextualSequence(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error, + WinPrAsn1Decoder* target) +{ + size_t ret, ret2; + WinPrAsn1_tag ftag; + WinPrAsn1Decoder content; + + WINPR_ASSERT(error); + + ret = WinPrAsn1DecPeekContextualTag(dec, &ftag, &content); + if (!ret || ftag != tagId) + return 0; + + ret2 = WinPrAsn1DecReadSequence(&content, target); + if (!ret2) + { + *error = TRUE; + return 0; + } + + *error = FALSE; + Stream_Seek(&dec->source, ret); + return ret; +} diff --git a/winpr/libwinpr/utils/test/CMakeLists.txt b/winpr/libwinpr/utils/test/CMakeLists.txt index 02ec09aeb..186b0dca3 100644 --- a/winpr/libwinpr/utils/test/CMakeLists.txt +++ b/winpr/libwinpr/utils/test/CMakeLists.txt @@ -19,6 +19,7 @@ set(${MODULE_PREFIX}_TESTS TestLinkedList.c TestListDictionary.c TestCmdLine.c + TestASN1.c TestWLog.c TestWLogCallback.c TestHashTable.c diff --git a/winpr/libwinpr/utils/test/TestASN1.c b/winpr/libwinpr/utils/test/TestASN1.c new file mode 100644 index 000000000..57fce41ba --- /dev/null +++ b/winpr/libwinpr/utils/test/TestASN1.c @@ -0,0 +1,275 @@ +#include +#include + +const BYTE boolContent[] = { 0x01, 0x01, 0xFF }; +const BYTE badBoolContent[] = { 0x01, 0x04, 0xFF }; + +const BYTE integerContent[] = { 0x02, 0x01, 0x02 }; +const BYTE badIntegerContent[] = { 0x02, 0x04, 0x02 }; + +const BYTE seqContent[] = { 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x1B, 0x44, + 0x69, 0x67, 0x69, 0x74, 0x61, 0x6C, 0x20, 0x53, 0x69, 0x67, + 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x43, 0x6F, 0x2E, 0x31 }; + +const BYTE contextualInteger[] = { 0xA0, 0x03, 0x02, 0x01, 0x02 }; + +const BYTE oidContent[] = { 0x06, 0x03, 0x55, 0x04, 0x0A }; +const BYTE badOidContent[] = { 0x06, 0x89, 0x55, 0x04, 0x0A }; +const BYTE oidValue[] = { 0x55, 0x04, 0x0A }; + +const BYTE ia5stringContent[] = { 0x16, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, + 0x63, 0x70, 0x73, 0x2E, 0x72, 0x6F, 0x6F, 0x74, 0x2D, + 0x78, 0x31, 0x2E, 0x6C, 0x65, 0x74, 0x73, 0x65, 0x6E, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x2E, 0x6F, 0x72, 0x67 }; + +const BYTE utctimeContent[] = { 0x17, 0x0D, 0x32, 0x31, 0x30, 0x33, 0x31, 0x37, + 0x31, 0x36, 0x34, 0x30, 0x34, 0x36, 0x5A }; + +int TestASN1Read(int argc, char* argv[]) +{ + WinPrAsn1Decoder decoder, seqDecoder; + wStream staticS; + WinPrAsn1_BOOL boolV; + WinPrAsn1_INTEGER integerV; + WinPrAsn1_OID oidV; + WinPrAsn1_IA5STRING ia5stringV; + WinPrAsn1_UTCTIME utctimeV; + WinPrAsn1_tag tag; + size_t len; + BOOL error; + + /* ============== Test INTEGERs ================ */ + Stream_StaticConstInit(&staticS, integerContent, sizeof(integerContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadInteger(&decoder, &integerV)) + return -1; + + Stream_StaticConstInit(&staticS, badIntegerContent, sizeof(badIntegerContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (WinPrAsn1DecReadInteger(&decoder, &integerV)) + return -1; + + /* ================ Test BOOL ================*/ + Stream_StaticConstInit(&staticS, boolContent, sizeof(boolContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadBoolean(&decoder, &boolV)) + return -10; + + Stream_StaticConstInit(&staticS, badBoolContent, sizeof(badBoolContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (WinPrAsn1DecReadBoolean(&decoder, &boolV)) + return -11; + + /* ================ Test OID ================*/ + Stream_StaticConstInit(&staticS, oidContent, sizeof(oidContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadOID(&decoder, &oidV) || oidV.len != 3 || + memcmp(oidV.data, oidValue, oidV.len)) + return -15; + WinPrAsn1FreeOID(&oidV); + + Stream_StaticConstInit(&staticS, badOidContent, sizeof(badOidContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (WinPrAsn1DecReadOID(&decoder, &oidV)) + return -15; + WinPrAsn1FreeOID(&oidV); + + Stream_StaticConstInit(&staticS, ia5stringContent, sizeof(ia5stringContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadIA5String(&decoder, &ia5stringV) || + strcmp(ia5stringV, "http://cps.root-x1.letsencrypt.org")) + return -16; + free(ia5stringV); + + /* ================ Test utc time ================*/ + Stream_StaticConstInit(&staticS, utctimeContent, sizeof(utctimeContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadUtcTime(&decoder, &utctimeV) || utctimeV.year != 2021 || + utctimeV.month != 3 || utctimeV.day != 17 || utctimeV.minute != 40 || utctimeV.tz != 'Z') + return -17; + + /* ================ Test sequence ================*/ + Stream_StaticConstInit(&staticS, seqContent, sizeof(seqContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadSequence(&decoder, &seqDecoder)) + return -20; + + Stream_StaticConstInit(&staticS, seqContent, sizeof(seqContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadTagLenValue(&decoder, &tag, &len, &seqDecoder)) + return -21; + + if (tag != ER_TAG_SEQUENCE) + return -22; + + if (!WinPrAsn1DecPeekTag(&seqDecoder, &tag) || tag != ER_TAG_OBJECT_IDENTIFIER) + return -23; + + /* ================ Test contextual ================*/ + Stream_StaticConstInit(&staticS, contextualInteger, sizeof(contextualInteger)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadContextualInteger(&decoder, 0, &error, &integerV)) + return -25; + + /* test reading a contextual integer that is not there (index 1). + * that should not touch the decoder read head and we shall be able to extract contextual tag 0 + * after that + */ + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (WinPrAsn1DecReadContextualInteger(&decoder, 1, &error, &integerV) || error) + return -26; + + if (!WinPrAsn1DecReadContextualInteger(&decoder, 0, &error, &integerV)) + return -27; + + return 0; +} + +static const BYTE oid1_val[] = { 1 }; +static const WinPrAsn1_OID oid1 = { sizeof(oid1_val), (BYTE*)oid1_val }; +static BYTE oid2_val[] = { 2, 2 }; +static WinPrAsn1_OID oid2 = { sizeof(oid2_val), oid2_val }; +static BYTE oid3_val[] = { 3, 3, 3 }; +static WinPrAsn1_OID oid3 = { sizeof(oid3_val), oid3_val }; +static BYTE oid4_val[] = { 4, 4, 4, 4 }; +static WinPrAsn1_OID oid4 = { sizeof(oid4_val), oid4_val }; + +int TestASN1Write(int argc, char* argv[]) +{ + wStream* s = NULL; + size_t expectedOuputSz; + int retCode = 100; + WinPrAsn1_UTCTIME utcTime; + WinPrAsn1_IA5STRING ia5string; + WinPrAsn1Encoder* enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + goto out; + + /* Let's encode something like: + * APP(3) + * SEQ2 + * OID1 + * OID2 + * SEQ3 + * OID3 + * OID4 + * + * [5] integer(200) + * [6] SEQ (empty) + * [7] UTC time (2016-03-17 16:40:41 UTC) + * [8] IA5String(test) + */ + + /* APP(3) */ + retCode = 101; + if (!WinPrAsn1EncAppContainer(enc, 3)) + goto out; + + /* SEQ2 */ + retCode = 102; + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + retCode = 103; + if (WinPrAsn1EncOID(enc, &oid1) != 3) + goto out; + + retCode = 104; + if (WinPrAsn1EncOID(enc, &oid2) != 4) + goto out; + + retCode = 105; + if (WinPrAsn1EncEndContainer(enc) != 9) + goto out; + + /* SEQ3 */ + retCode = 110; + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + retCode = 111; + if (WinPrAsn1EncOID(enc, &oid3) != 5) + goto out; + + retCode = 112; + if (WinPrAsn1EncOID(enc, &oid4) != 6) + goto out; + + retCode = 113; + if (WinPrAsn1EncEndContainer(enc) != 13) + goto out; + + /* [5] integer(200) */ + retCode = 114; + if (WinPrAsn1EncContextualInteger(enc, 5, 200) != 6) + goto out; + + /* [6] SEQ (empty) */ + retCode = 115; + if (!WinPrAsn1EncContextualSeqContainer(enc, 6)) + goto out; + + retCode = 116; + if (WinPrAsn1EncEndContainer(enc) != 4) + goto out; + + /* [7] UTC time (2016-03-17 16:40:41 UTC) */ + retCode = 117; + utcTime.year = 2016; + utcTime.month = 3; + utcTime.day = 17; + utcTime.hour = 16; + utcTime.minute = 40; + utcTime.second = 41; + utcTime.tz = 'Z'; + if (WinPrAsn1EncContextualUtcTime(enc, 7, &utcTime) != 17) + goto out; + + /* [8] IA5String(test) */ + retCode = 118; + ia5string = "test"; + if (!WinPrAsn1EncContextualContainer(enc, 8)) + goto out; + + retCode = 119; + if (WinPrAsn1EncIA5String(enc, ia5string) != 6) + goto out; + + retCode = 120; + if (WinPrAsn1EncEndContainer(enc) != 8) + goto out; + + /* close APP */ + expectedOuputSz = 24 + 6 + 4 + 17 + 8; + retCode = 200; + if (WinPrAsn1EncEndContainer(enc) != expectedOuputSz) + goto out; + + /* let's output the result */ + retCode = 201; + s = Stream_New(NULL, 1024); + if (!s) + goto out; + + retCode = 202; + if (!WinPrAsn1EncToStream(enc, s) || Stream_GetPosition(s) != expectedOuputSz) + goto out; + retCode = 0; + + /*winpr_HexDump("", WLOG_ERROR, Stream_Buffer(s), Stream_GetPosition(s));*/ +out: + if (s) + Stream_Free(s, TRUE); + WinPrAsn1Encoder_Free(&enc); + return retCode; +} + +int TestASN1(int argc, char* argv[]) +{ + int ret = TestASN1Read(argc, argv); + if (ret) + return ret; + + return TestASN1Write(argc, argv); +}