Skip to content

Commit

Permalink
Updated ASN1 Writer Implementation. (#7832)
Browse files Browse the repository at this point in the history
New implementation stores an array of deferred lengths as part of the state.
With the new implementation it is no longer required to pre-allocate a larger
output buffer to store the deferred-length list.

The maximum supported depth of constructed/encapsulated types is set to 10,
which is the maximum size for deferred length array.
In the current code, the most complicated use case (ASN1 CHIP Certificates
encoding/decoding) requires depth of 7.
  • Loading branch information
emargolis authored and pull[bot] committed Sep 3, 2021
1 parent 1a97284 commit 1013083
Show file tree
Hide file tree
Showing 10 changed files with 1,011 additions and 1,073 deletions.
11 changes: 1 addition & 10 deletions src/credentials/CHIPCert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -906,29 +906,20 @@ CHIP_ERROR ConvertIntegerRawToDER(const uint8_t * rawInt, uint16_t rawIntLen, ui
CHIP_ERROR ConvertECDSASignatureRawToDER(const uint8_t * rawSig, uint16_t rawSigLen, uint8_t * derSig, const uint16_t derSigBufSize,
uint16_t & derSigLen)
{
static constexpr size_t kMaxBytesForDeferredLenList = sizeof(uint8_t *) + // size of a single pointer in the deferred list
4 + // extra memory allocated for the deferred length field (kLengthFieldReserveSize - 1)
3; // the deferred length list is alligned to 32bit boundary

uint8_t localDERSigBuf[kMax_ECDSA_Signature_Length + kMaxBytesForDeferredLenList];
ASN1Writer writer;

VerifyOrReturnError(rawSig != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(rawSigLen > 0, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(derSig != nullptr, CHIP_ERROR_INVALID_ARGUMENT);

writer.Init(localDERSigBuf, sizeof(localDERSigBuf));
writer.Init(derSig, derSigBufSize);

ReturnErrorOnFailure(ConvertECDSASignatureRawToDER(rawSig, rawSigLen, writer));

ReturnErrorOnFailure(writer.Finalize());

derSigLen = writer.GetLengthWritten();

VerifyOrReturnError(derSigLen <= derSigBufSize, CHIP_ERROR_BUFFER_TOO_SMALL);

memcpy(derSig, localDERSigBuf, derSigLen);

return CHIP_NO_ERROR;
}

Expand Down
Empty file modified src/credentials/CHIPCert.h
100755 → 100644
Empty file.
1,782 changes: 887 additions & 895 deletions src/credentials/tests/CHIPCert_test_vectors.cpp

Large diffs are not rendered by default.

94 changes: 52 additions & 42 deletions src/credentials/tests/TestChipCert.cpp

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions src/lib/asn1/ASN1.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ namespace ASN1 {
#include <asn1/ASN1OID.h>
#endif

static constexpr size_t kMaxConstructedAndEncapsulatedTypesDepth = 10;

enum ASN1TagClasses
{
kASN1TagClass_Universal = 0x00,
Expand Down Expand Up @@ -125,7 +127,7 @@ class DLL_EXPORT ASN1Reader
ASN1_ERROR GetBitString(uint32_t & outVal);

private:
static constexpr size_t kMaxContextDepth = 32;
static constexpr size_t kMaxContextDepth = kMaxConstructedAndEncapsulatedTypesDepth;

struct ASN1ParseContext
{
Expand Down Expand Up @@ -188,10 +190,13 @@ class DLL_EXPORT ASN1Writer
ASN1_ERROR PutValue(uint8_t cls, uint32_t tag, bool isConstructed, chip::TLV::TLVReader & val);

private:
static constexpr size_t kMaxDeferredLengthDepth = kMaxConstructedAndEncapsulatedTypesDepth;

uint8_t * mBuf;
uint8_t * mBufEnd;
uint8_t * mWritePoint;
uint8_t ** mDeferredLengthList;
uint8_t * mDeferredLengthLocations[kMaxDeferredLengthDepth];
uint8_t mDeferredLengthCount;

ASN1_ERROR EncodeHead(uint8_t cls, uint32_t tag, bool isConstructed, int32_t len);
ASN1_ERROR WriteDeferredLength(void);
Expand Down
176 changes: 62 additions & 114 deletions src/lib/asn1/ASN1Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <core/CHIPEncoding.h>
#include <core/CHIPTLV.h>
#include <support/CodeUtils.h>
#include <support/SafeInt.h>

namespace chip {
namespace ASN1 {
Expand All @@ -46,70 +47,30 @@ using namespace chip::Encoding;

enum
{
kLengthFieldReserveSize = 5,
kMaxElementLength = INT32_MAX,
kUnkownLength = -1,
kLengthFieldReserveSize = 1,
kUnknownLength = -1,
kUnknownLengthMarker = 0xFF
};

void ASN1Writer::Init(uint8_t * buf, uint32_t maxLen)
{
mBuf = buf;
mWritePoint = buf;
mBufEnd = buf + maxLen;
mBufEnd = reinterpret_cast<uint8_t *>(reinterpret_cast<uintptr_t>(mBufEnd) & ~3); // align on 32bit boundary
mDeferredLengthList = reinterpret_cast<uint8_t **>(mBufEnd);
mBuf = buf;
mWritePoint = buf;
mBufEnd = buf + maxLen;
mDeferredLengthCount = 0;
}

void ASN1Writer::InitNullWriter(void)
{
mBuf = nullptr;
mWritePoint = nullptr;
mBufEnd = nullptr;
mDeferredLengthList = nullptr;
mBuf = nullptr;
mWritePoint = nullptr;
mBufEnd = nullptr;
mDeferredLengthCount = 0;
}

ASN1_ERROR ASN1Writer::Finalize()
{
if (mBuf != nullptr)
{
uint8_t * compactPoint = mBuf;
uint8_t * spanStart = mBuf;

for (uint8_t ** listEntry = reinterpret_cast<uint8_t **>(mBufEnd); listEntry > mDeferredLengthList;)
{
uint8_t * lenField = *--listEntry;
uint8_t lenFieldFirstByte = *lenField;

if (lenFieldFirstByte == kUnknownLengthMarker)
return ASN1_ERROR_INVALID_STATE;

uint8_t lenOfLen = (lenFieldFirstByte < 128) ? 1 : (lenFieldFirstByte & 0x7f) + 1;

uint8_t * spanEnd = lenField + lenOfLen;

if (spanStart == compactPoint)
compactPoint = spanEnd;
else
{
uint32_t spanLen = spanEnd - spanStart;
memmove(compactPoint, spanStart, spanLen);
compactPoint += spanLen;
}

spanStart = lenField + kLengthFieldReserveSize;
}

if (spanStart > compactPoint)
{
uint32_t spanLen = mWritePoint - spanStart;
memmove(compactPoint, spanStart, spanLen);
compactPoint += spanLen;
}

mWritePoint = compactPoint;
}

// TODO: This method is not required and can be deprecated.
return ASN1_NO_ERROR;
}

Expand Down Expand Up @@ -236,7 +197,9 @@ ASN1_ERROR ASN1Writer::PutBitString(uint32_t val)
ReturnErrorOnFailure(EncodeHead(kASN1TagClass_Universal, kASN1UniversalTag_BitString, false, len));

if (val == 0)
{
mWritePoint[0] = 0;
}
else
{
mWritePoint[1] = ReverseBits(static_cast<uint8_t>(val));
Expand Down Expand Up @@ -346,7 +309,7 @@ ASN1_ERROR ASN1Writer::PutConstructedType(const uint8_t * val, uint16_t valLen)

ASN1_ERROR ASN1Writer::StartConstructedType(uint8_t cls, uint32_t tag)
{
return EncodeHead(cls, tag, true, kUnkownLength);
return EncodeHead(cls, tag, true, kUnknownLength);
}

ASN1_ERROR ASN1Writer::EndConstructedType()
Expand All @@ -359,15 +322,14 @@ ASN1_ERROR ASN1Writer::StartEncapsulatedType(uint8_t cls, uint32_t tag, bool bit
// Do nothing for a null writer.
VerifyOrReturnError(mBuf != nullptr, ASN1_NO_ERROR);

ReturnErrorOnFailure(EncodeHead(cls, tag, false, kUnkownLength));
ReturnErrorOnFailure(EncodeHead(cls, tag, false, kUnknownLength));

// If the encapsulating type is BIT STRING, encode the unused bit count field. Since the BIT
// STRING contains an ASN.1 DER encoding, and ASN.1 DER encodings are always multiples of 8 bits,
// the unused bit count is always 0.
if (bitStringEncoding)
{
if (mWritePoint == reinterpret_cast<uint8_t *>(mDeferredLengthList))
return ASN1_ERROR_OVERFLOW;
VerifyOrReturnError(mWritePoint < mBufEnd, ASN1_ERROR_OVERFLOW);
*mWritePoint++ = 0;
}

Expand Down Expand Up @@ -420,42 +382,38 @@ ASN1_ERROR ASN1Writer::EncodeHead(uint8_t cls, uint32_t tag, bool isConstructed,
// Only tags <= 31 supported. The implication of this is that encoded tags are exactly 1 byte long.
VerifyOrReturnError(tag <= 0x1F, ASN1_ERROR_UNSUPPORTED_ENCODING);

// Only positive and kUnkownLength values are supported for len input.
VerifyOrReturnError(len >= 0 || len == kUnkownLength, ASN1_ERROR_UNSUPPORTED_ENCODING);
// Only positive and kUnknownLength values are supported for len input.
VerifyOrReturnError(len >= 0 || len == kUnknownLength, ASN1_ERROR_UNSUPPORTED_ENCODING);

// Compute the number of bytes required to encode the length.
bytesForLen = BytesForLength(len);

// If the element length is unknown, allocate a new entry in the deferred-length list.
//
// The deferred-length list is a list of "pointers" (represented as offsets into mBuf)
// to length fields for which the length of the element was unknown at the time the element
// head was written. Examples include constructed types such as SEQUENCE and SET, as well
// non-constructed types that encapsulate other ASN.1 types (e.g. OCTET STRINGS that contain
// BER/DER encodings). The final lengths are filled in later, at the time the encoding is
// complete (e.g. when EndConstructed() is called).
//
if (len == kUnkownLength)
mDeferredLengthList--;

// Make sure there's enough space to encode the entire value without bumping into the deferred length
// list at the end of the buffer.
totalLen = 1 + bytesForLen + (len != kUnkownLength ? len : 0);
VerifyOrReturnError((mWritePoint + totalLen) <= reinterpret_cast<uint8_t *>(mDeferredLengthList), ASN1_ERROR_OVERFLOW);
// Make sure there's enough space to encode the entire value.
totalLen = 1 + bytesForLen + (len != kUnknownLength ? len : 0);
VerifyOrReturnError((mWritePoint + totalLen) <= mBufEnd, ASN1_ERROR_OVERFLOW);

// Write the tag byte.
*mWritePoint++ = cls | (isConstructed ? 0x20 : 0) | tag;

// Encode the length if it is known.
if (len != kUnkownLength)
if (len != kUnknownLength)
{
EncodeLength(mWritePoint, bytesForLen, len);

}
// ... otherwise place a marker in the first byte of the length to indicate that the length is unknown
// and save a pointer to the length field in the deferred-length list.
// and save a pointer to the length field in the deferred-length array.
//
// The deferred-length is an array of "pointers" to length fields for which the length of the
// element was unknown at the time the element head was written. Examples include constructed
// types such as SEQUENCE and SET, as well non-constructed types that encapsulate other ASN.1 types
// (e.g. OCTET STRINGS that contain BER/DER encodings). The final lengths are filled in later,
// at the time the encoding of the element is complete (e.g. when EndConstructed() is called).
else
{
*mWritePoint = kUnknownLengthMarker;
*mDeferredLengthList = mWritePoint;
VerifyOrReturnError(mDeferredLengthCount < kMaxDeferredLengthDepth, ASN1_ERROR_INVALID_STATE);

*mWritePoint = kUnknownLengthMarker;
mDeferredLengthLocations[mDeferredLengthCount++] = mWritePoint;
}

mWritePoint += bytesForLen;
Expand All @@ -465,52 +423,40 @@ ASN1_ERROR ASN1Writer::EncodeHead(uint8_t cls, uint32_t tag, bool isConstructed,

ASN1_ERROR ASN1Writer::WriteDeferredLength()
{
uint8_t ** listEntry;
uint32_t lenAdj;

// Do nothing for a null writer.
VerifyOrReturnError(mBuf != nullptr, ASN1_NO_ERROR);

lenAdj = kLengthFieldReserveSize;
VerifyOrReturnError(mDeferredLengthCount > 0, ASN1_ERROR_INVALID_STATE);

// Scan the deferred-length list in reverse order looking for the most recent entry where
// the length is still unknown. This entry represents the "container" element whose encoding
// is now complete.
for (listEntry = mDeferredLengthList; listEntry < reinterpret_cast<uint8_t **>(mBufEnd); listEntry++)
{
// Get a pointer to the deferred-length field.
uint8_t * lenField = *listEntry;
uint8_t * lenField = mDeferredLengthLocations[mDeferredLengthCount - 1];

// Get the first byte of the length field.
uint8_t lenFieldFirstByte = *lenField;
VerifyOrReturnError(*lenField == kUnknownLengthMarker, ASN1_ERROR_INVALID_STATE);

// If the length is marked as unknown...
if (lenFieldFirstByte == kUnknownLengthMarker)
{
// Compute the final length of the element's value (3 = bytes reserved for length).
uint32_t elemLen = (mWritePoint - lenField) - lenAdj;
// Compute the length of the element's value.
uint32_t elemLen = (mWritePoint - lenField) - kLengthFieldReserveSize;

// Return an error if the length exceeds the maximum value that can be encoded in the
// space reserved for the length.
VerifyOrReturnError(elemLen <= kMaxElementLength, ASN1_ERROR_LENGTH_OVERFLOW);
VerifyOrReturnError(CanCastTo<int32_t>(elemLen), ASN1_ERROR_LENGTH_OVERFLOW);

// Encode the final length of the element, overwriting the unknown length marker
// in the process. Note that the number of bytes consumed by the final length field
// may be smaller than the space that was reserved for the field. This will be fixed
// up when the Finalize() method is called.
uint8_t bytesForLen = BytesForLength(static_cast<int32_t>(elemLen));
EncodeLength(lenField, bytesForLen, elemLen);
uint8_t bytesForLen = BytesForLength(static_cast<int32_t>(elemLen));

return ASN1_NO_ERROR;
}
else
{
uint8_t bytesForLen = (lenFieldFirstByte < 128) ? 1 : (lenFieldFirstByte & 0x7f) + 1;
lenAdj += (kLengthFieldReserveSize - bytesForLen);
}
// Move the element data if the number of bytes consumed by the final length field
// is different than the space that was reserved for the field.
if (bytesForLen != kLengthFieldReserveSize)
{
mWritePoint += (bytesForLen - kLengthFieldReserveSize);

VerifyOrReturnError(mWritePoint <= mBufEnd, ASN1_ERROR_OVERFLOW);

memmove(lenField + bytesForLen, lenField + kLengthFieldReserveSize, elemLen);
}

return ASN1_ERROR_INVALID_STATE;
// Encode the final length of the element, overwriting the unknown length marker
// in the process.
EncodeLength(lenField, bytesForLen, elemLen);

mDeferredLengthCount--;

return ASN1_NO_ERROR;
}

/**
Expand All @@ -522,7 +468,7 @@ ASN1_ERROR ASN1Writer::WriteDeferredLength()
*/
uint8_t ASN1Writer::BytesForLength(int32_t len)
{
if (len == kUnkownLength)
if (len == kUnknownLength)
return kLengthFieldReserveSize;
if (len < 128)
return 1;
Expand All @@ -538,7 +484,9 @@ uint8_t ASN1Writer::BytesForLength(int32_t len)
void ASN1Writer::EncodeLength(uint8_t * buf, uint8_t bytesForLen, int32_t lenToEncode)
{
if (bytesForLen == 1)
{
buf[0] = static_cast<uint8_t>(lenToEncode);
}
else
{
--bytesForLen;
Expand Down
Empty file modified src/tools/chip-cert/BUILD.gn
100755 → 100644
Empty file.
Empty file modified src/tools/chip-cert/Cmd_ConvertKey.cpp
100755 → 100644
Empty file.
Empty file modified src/tools/chip-cert/Cmd_GenCert.cpp
100755 → 100644
Empty file.
12 changes: 2 additions & 10 deletions src/tools/chip-cert/chip-cert.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,8 @@ enum KeyFormat

enum
{
kMaxChipCertBufSize = 450, // Maximum size of a buffer needed to hold CHIP TLV encoded certificates.
// CHIP certificate in TLV format shouldn't exceed 400 bytes.
// chip-cert tool allows support for larger size certificates,
// which might be generated for negative testing purposes.
kMaxX509CertBufSize = 1200, // Maximum size of a buffer needed to hold/encode X.509 certificates in DER form.
// CHIP certificate in X.509 DER form shouldn't exceed 600 bytes.
// chip-cert tool allows support for larger size certificates,
// which might be generated for negative testing purposes.
// Also, CHIP to X.509 certificate convertor (ASN1 encoder) requires additional
// space in the buffer to store the deffered length list.
kMaxChipCertBufSize = 400, // Maximum size of a buffer needed to hold CHIP TLV encoded certificates.
kMaxX509CertBufSize = 600, // Maximum size of a buffer needed to hold/encode X.509 certificates in DER form.
};

struct FutureExtension
Expand Down

0 comments on commit 1013083

Please sign in to comment.