core/riot/reference/RiotDerEnc.c (461 lines of code) (raw):

/* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See LICENSE in the project root. */ #include <stdbool.h> #include <stdint.h> #include <string.h> #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> // TODO: REMOVE THIS #else #include "platform_api.h" #endif #include "include/RiotBase64.h" #include "include/RiotDerEnc.h" // // This file contains basic DER-encoding routines that are sufficient to create // RIoT X.509 certificates. A few corners are cut (and noted) in the interests // of small code footprint and simplicity. // // Routines in this file encode the following types: // SEQUENCE // SET // INTEGER // OID // BOOL // PrintableString // UTF8String // UTCTime // GeneralizedTime // // Assert-less assert #define ASRT(_X) if(!(_X)) {goto Error;} // The encoding routines need to check that the encoded data will fit in the // buffer. The following macros do (conservative) checks because it's hard to // properly test low-buffer situations. CHECK_SPACE is appropriate for short // additions. CHECK_SPACE2 when larger objects are being added (and the length // is known.) #define CHECK_SPACE(_X) if((_X->Length-_X->Position)<32) {goto Error;} #define CHECK_SPACE2(_X, _N) if(((_X->Length-_X->Position)+(_N))<32) {goto Error;} static int GetIntEncodedNumBytes ( int Val) // Returns the number of bytes needed to DER encode a number. If the number // is less then 127, a single byte is used. Otherwise the DER rule is first // byte is 0x80|NumBytes, followed by the number in network byte-order. Note // that the routines in this library only handle lengths up to 16K Bytes. { ASRT (Val < 166536); if (Val < 128) { return 1; } if (Val < 256) { return 2; } return 3; Error: return -1; } static int EncodeInt ( uint8_t *Buffer, int Val) // DER-encode Val into buffer. Function assumes the caller knows how many // bytes it will need (e.g., from GetIntEncodedNumBytes). { ASRT (Val < 166536); if (Val < 128) { Buffer[0] = (uint8_t) Val; return 0; } if (Val < 256) { Buffer[0] = 0x81; Buffer[1] = (uint8_t) Val; return 0; } Buffer[0] = 0x82; Buffer[1] = (uint8_t) (Val / 256); Buffer[2] = Val % 256; return 0; Error: return -1; } void DERInitContext ( DERBuilderContext *Context, uint8_t *Buffer, size_t Length) // Intialize the builder context. The caller manages the encoding buffer. // Note that the encoding routines do conservative checks that the encoding // will fit, so approximately 30 extra bytes are needed. Note that if an // encoding routine fails because the buffer is too small, the buffer will // be in an indeterminate state, and the encoding must be restarted. { int j; Context->Buffer = Buffer; Context->Length = Length; Context->Position = 0; memset (Buffer, 0, Length); for (j = 0; j < DER_MAX_NESTED; j++) { Context->CollectionStart[j] = -1; } Context->CollectionPos = 0; return; } size_t DERGetEncodedLength ( DERBuilderContext *Context) // Get the length of encoded data. { return Context->Position; } int DERAddOID ( DERBuilderContext *Context, const int *Values) // Add an OID. The OID is an int-array (max 16) terminated with -1 { int j, k; int lenPos, digitPos = 0; int val, digit; int numValues = 0; for (j = 0; j < 16; j++) { if (Values[j] < 0) { break; } numValues++; } // Sanity check ASRT (numValues < 16); // Note that we don't know how many bytes the actual encoding will be // so we also check as we fill the buffer. CHECK_SPACE (Context); Context->Buffer[Context->Position++] = 6; // Save space for length (only <128 supported) lenPos = Context->Position; Context->Position++; // DER-encode the OID, first octet is special val = (numValues == 1) ? 0 : Values[1]; Context->Buffer[Context->Position++] = (uint8_t) (Values[0] * 40 + val); // Others are base-128 encoded with the most significant bit of each byte, // apart from the least significant byte, set to 1. if (numValues >= 2) { uint8_t digits[5] = {0}; for (j = 2; j < numValues; j++) { digitPos = 0; val = Values[j]; // Convert to B128 while (true) { digit = val % 128; digits[digitPos++] = (uint8_t) digit; val = val / 128; if (val == 0) { break; } } // Reverse into the buffer, setting the MSB as needed. for (k = digitPos - 1; k >= 0; k--) { val = digits[k]; if (k != 0) { val += 128; } Context->Buffer[Context->Position++] = (uint8_t) val; } CHECK_SPACE (Context); } } Context->Buffer[lenPos] = (uint8_t) (Context->Position - 1 - lenPos); return 0; Error: return -1; } int DERAddEncodedOID ( DERBuilderContext *Context, const uint8_t *Oid, size_t OidLength) { uint32_t j; ASRT (OidLength < 127); CHECK_SPACE2 (Context, OidLength); Context->Buffer[Context->Position++] = 0x06; Context->Buffer[Context->Position++] = (uint8_t) OidLength; for (j = 0; j < OidLength; j++) { Context->Buffer[Context->Position++] = Oid[j]; } return 0; Error: return -1; } int DERAddString ( DERBuilderContext *Context, const char *Str, uint8_t Tag) { uint32_t j, numChar = (uint32_t) strlen (Str); ASRT (numChar < 127); CHECK_SPACE2 (Context, numChar); Context->Buffer[Context->Position++] = Tag; Context->Buffer[Context->Position++] = (uint8_t) numChar; for (j = 0; j < numChar; j++) { Context->Buffer[Context->Position++] = Str[j]; } return 0; Error: return -1; } int DERAddUTF8String ( DERBuilderContext *Context, const char *Str) { return DERAddString (Context, Str, 0x0c); } int DERAddPrintableString ( DERBuilderContext *Context, const char *Str) { return DERAddString (Context, Str, 0x13); } int DERAddIA5String ( DERBuilderContext *Context, const char *Str) { return DERAddString (Context, Str, 0x16); } int DERAddTime ( DERBuilderContext *Context, const char *Str) // Format of time can be UTCTime (YYMMDDhhmmssZ) or GeneralizedTime (YYYYMMDDHHMMSSZ) { uint32_t j, numChar = (uint32_t) strlen (Str); ASRT ((numChar == 13) || (numChar == 15)); CHECK_SPACE (Context); if (numChar == 13) { Context->Buffer[Context->Position++] = 0x17; // UTCTime } else { Context->Buffer[Context->Position++] = 0x18; // GeneralizedTime } Context->Buffer[Context->Position++] = (uint8_t) numChar; for (j = 0; j < numChar; j++) { Context->Buffer[Context->Position++] = Str[j]; } return 0; Error: return -1; } int DERAddTaggedIntegerFromArray ( DERBuilderContext *Context, const uint8_t *Val, uint32_t NumBytes, uint8_t Tag) // Input integer is assumed unsigned with most signficant byte first. // A leading zero will be added if the most significant input bit is set. // Leading zeros in the input number will be removed. { uint32_t j, numLeadingZeros = 0; bool negative; ASRT (NumBytes < 128); CHECK_SPACE2 (Context, NumBytes); for (j = 0; j < NumBytes; j++) { if (Val[j] != 0) { break; } numLeadingZeros++; } negative = Val[numLeadingZeros] >= 128; Context->Buffer[Context->Position++] = Tag; if (NumBytes == numLeadingZeros) { //integer is zero Context->Buffer[Context->Position++] = 1; Context->Buffer[Context->Position++] = 0; } else { if (negative) { Context->Buffer[Context->Position++] = (uint8_t) (NumBytes - numLeadingZeros + 1); Context->Buffer[Context->Position++] = 0; } else { Context->Buffer[Context->Position++] = (uint8_t) (NumBytes - numLeadingZeros); } for (j = numLeadingZeros; j < NumBytes; j++) { Context->Buffer[Context->Position++] = Val[j]; } } return 0; Error: return -1; } int DERAddIntegerFromArray ( DERBuilderContext *Context, const uint8_t *Val, uint32_t NumBytes) { return DERAddTaggedIntegerFromArray (Context, Val, NumBytes, 0x02); } int DERAddTaggedInteger ( DERBuilderContext *Context, int Val, uint8_t Tag) { long valx = platform_htonl (Val); // TODO: REMOVE USAGE int res = DERAddTaggedIntegerFromArray (Context, (uint8_t*) &valx, 4, Tag); return res; } int DERAddInteger ( DERBuilderContext *Context, int Val) { return DERAddTaggedInteger (Context, Val, 0x02); } int DERAddShortExplicitInteger ( DERBuilderContext *Context, int Val) { long valx; ASRT (Val < 127); Context->Buffer[Context->Position++] = 0xA0; Context->Buffer[Context->Position++] = 3; valx = platform_htonl (Val); return (DERAddIntegerFromArray (Context, (uint8_t*) &valx, 4)); Error: return -1; } int DERAddBoolean ( DERBuilderContext *Context, bool Val) { CHECK_SPACE (Context); Context->Buffer[Context->Position++] = 0x01; Context->Buffer[Context->Position++] = 0x01; if (Val) { Context->Buffer[Context->Position++] = 0xFF; } else { Context->Buffer[Context->Position++] = 0x00; } return 0; Error: return -1; } int DERAddBitString ( DERBuilderContext *Context, const uint8_t *BitString, size_t BitStringNumBytes) { return DERAddNamedBitString (Context, BitString, BitStringNumBytes, BitStringNumBytes * 8); } int DERAddNamedBitString ( DERBuilderContext *Context, const uint8_t *BitString, size_t BitStringNumBytes, size_t bits) { int len = BitStringNumBytes + 1; size_t unused_bits = (BitStringNumBytes * 8) - bits; if (unused_bits > 7) { goto Error; } CHECK_SPACE2 (Context, BitStringNumBytes); Context->Buffer[Context->Position++] = 0x03; EncodeInt (Context->Buffer + Context->Position, len); Context->Position += GetIntEncodedNumBytes (len); Context->Buffer[Context->Position++] = (uint8_t) unused_bits; memcpy (Context->Buffer + Context->Position, BitString, BitStringNumBytes); Context->Position += BitStringNumBytes; return 0; Error: return -1; } int DERAddOctetString ( DERBuilderContext *Context, const uint8_t *OctetString, size_t OctetStringLen) { CHECK_SPACE2 (Context, OctetStringLen); Context->Buffer[Context->Position++] = 0x04; EncodeInt (Context->Buffer + Context->Position, OctetStringLen); Context->Position += GetIntEncodedNumBytes (OctetStringLen); memcpy (Context->Buffer + Context->Position, OctetString, OctetStringLen); Context->Position += OctetStringLen; return 0; Error: return -1; } int DERStartConstructed ( DERBuilderContext *Context, uint8_t Tag) { CHECK_SPACE (Context); ASRT (Context->CollectionPos < DER_MAX_NESTED); Context->Buffer[Context->Position++] = Tag; // Note that no space is left for the length field. The length field // is added at DEREndSequence when we know how many bytes are needed. Context->CollectionStart[Context->CollectionPos++] = Context->Position; return 0; Error: return -1; } int DERStartSequenceOrSet ( DERBuilderContext *Context, bool Sequence) { uint8_t tp = Sequence ? 0x30 : 0x31; return DERStartConstructed (Context, tp); } int DERStartExplicit ( DERBuilderContext *Context, uint32_t Num) { CHECK_SPACE (Context); ASRT (Context->CollectionPos < DER_MAX_NESTED); Context->Buffer[Context->Position++] = 0xA0 + (uint8_t) Num; Context->CollectionStart[Context->CollectionPos++] = Context->Position; return 0; Error: return -1; } int DERAddAuthKeyBitString ( DERBuilderContext *Context, const uint8_t *BitString, size_t BitStringLen) { CHECK_SPACE2 (Context, BitStringLen); Context->Buffer[Context->Position++] = 0x80; EncodeInt (Context->Buffer + Context->Position, BitStringLen); Context->Position += GetIntEncodedNumBytes (BitStringLen); memcpy (Context->Buffer + Context->Position, BitString, BitStringLen); Context->Position += BitStringLen; return 0; Error: return -1; } int DERStartEnvelopingOctetString ( DERBuilderContext *Context) { CHECK_SPACE (Context); ASRT (Context->CollectionPos < DER_MAX_NESTED); Context->Buffer[Context->Position++] = 0x04; Context->CollectionStart[Context->CollectionPos++] = Context->Position; return 0; Error: return -1; } int DERStartEnvelopingBitString ( DERBuilderContext *Context) { CHECK_SPACE (Context); ASRT (Context->CollectionPos < DER_MAX_NESTED); Context->Buffer[Context->Position++] = 0x03; // The payload includes the numUnusedBits (always zero, for our encodings). Context->CollectionStart[Context->CollectionPos++] = Context->Position; // No unused bits Context->Buffer[Context->Position++] = 0; return 0; Error: return -1; } int DERPopNesting ( DERBuilderContext *Context) { int startPos, numBytes, encodedLenSize; CHECK_SPACE (Context); ASRT (Context->CollectionPos > 0); startPos = Context->CollectionStart[--Context->CollectionPos]; numBytes = Context->Position - startPos; // How big is the length field? encodedLenSize = GetIntEncodedNumBytes (numBytes); // Make space for the length memmove (Context->Buffer + startPos + encodedLenSize, Context->Buffer + startPos, numBytes); // Fill in the length EncodeInt (Context->Buffer + startPos, numBytes); // Bump up the next-pointer Context->Position += encodedLenSize; return 0; Error: return -1; } int DERTbsToCert ( DERBuilderContext *Context) // This function assumes that Context contains a fully-formed "to be signed" // region of a certificate. DERTbsToCert copies the existing TBS region into< // an enclosing SEQUENCE. This prepares the context to receive the signature // block to make a fully-formed certificate. { ASRT (Context->CollectionPos == 0); CHECK_SPACE (Context); // Move up one byte to leave room for the SEQUENCE tag. // The length is filled in when the sequence is popped. memmove (Context->Buffer + 1, Context->Buffer, Context->Position); // Fix up the length Context->Position++; // Add a sequence tag Context->Buffer[0] = 0x30; // Push the sequence into the collection stack Context->CollectionStart[Context->CollectionPos++] = 1; // Context now contains a TBS region inside a SEQUENCE. Signature block next. return 0; Error: return -1; } int DERGetNestingDepth ( DERBuilderContext *Context) { return Context->CollectionPos; } typedef struct { uint16_t hLen; uint16_t fLen; const char *header; const char *footer; } PEMHeadersFooters; // We only have a small subset of potential PEM encodings const PEMHeadersFooters PEMhf[LAST_CERT_TYPE] = { {28, 26, "-----BEGIN CERTIFICATE-----\n", "-----END CERTIFICATE-----\n"}, {27, 25, "-----BEGIN PUBLIC KEY-----\n", "-----END PUBLIC KEY-----\n\0"}, {31, 29, "-----BEGIN EC PRIVATE KEY-----\n", "-----END EC PRIVATE KEY-----\n"}, {36, 34, "-----BEGIN CERTIFICATE REQUEST-----\n", "-----END CERTIFICATE REQUEST-----\n"} }; int DERtoPEM ( DERBuilderContext *Context, uint32_t Type, char *PEM, uint32_t *Length, const struct base64_engine *base64) // Note that this function does not support extra header information for // encrypted keys. Expand the header buffer to ~128 bytes to support this. { uint32_t b64Len, reqLen; int status; // Parameter validation if (!(Context) || !(Type < LAST_CERT_TYPE) || !(PEM) || !(Length)) { return -1; } // Calculate required length for output buffer b64Len = Base64Length (Context->Position); reqLen = b64Len + PEMhf[Type].hLen + PEMhf[Type].fLen; // Validate length of output buffer if (*Length < reqLen) { *Length = reqLen; return -1; } // Place header memcpy (PEM, PEMhf[Type].header, PEMhf[Type].hLen); PEM += PEMhf[Type].hLen; // Encode bytes status = base64->encode (base64, Context->Buffer, Context->Position, (unsigned char*) PEM, *Length); if (status != 0) { return -1; } PEM += b64Len; // Place footer memcpy (PEM, PEMhf[Type].footer, PEMhf[Type].fLen); PEM += PEMhf[Type].fLen; // Output buffer length *Length = reqLen; return 0; } int DERAddNull ( DERBuilderContext *Context) { CHECK_SPACE (Context); Context->Buffer[Context->Position++] = 0x5; Context->Buffer[Context->Position++] = 0x0; return 0; Error: return -1; } int DERAddPublicKey ( DERBuilderContext *Context, const uint8_t *key, size_t key_len) { return DERAddDER (Context, key, key_len); } int DERAddDER ( DERBuilderContext *Context, const uint8_t *der, size_t length) { CHECK_SPACE2 (Context, length); memcpy (&Context->Buffer[Context->Position], der, length); Context->Position += length; return 0; Error: return -1; }