From 1183659fc76b55bc3a090ee565d7544878bee70f Mon Sep 17 00:00:00 2001 From: Evgeny Margolis Date: Thu, 15 Sep 2022 07:27:59 -0700 Subject: [PATCH] [chip-cert] Added Print Certification Declaration (CD) Command to the Tool. (#22605) --- src/lib/core/CHIPTLVDebug.cpp | 12 +- src/lib/core/tests/TestCHIPTLV.cpp | 54 +++++- src/tools/chip-cert/BUILD.gn | 1 + src/tools/chip-cert/Cmd_PrintCD.cpp | 277 ++++++++++++++++++++++++++++ src/tools/chip-cert/README.md | 41 ++++ src/tools/chip-cert/chip-cert.cpp | 6 + src/tools/chip-cert/chip-cert.h | 1 + 7 files changed, 386 insertions(+), 6 deletions(-) create mode 100644 src/tools/chip-cert/Cmd_PrintCD.cpp diff --git a/src/lib/core/CHIPTLVDebug.cpp b/src/lib/core/CHIPTLVDebug.cpp index ab70ffbf783c90..5b1885879f17b9 100644 --- a/src/lib/core/CHIPTLVDebug.cpp +++ b/src/lib/core/CHIPTLVDebug.cpp @@ -68,13 +68,11 @@ static void DumpHandler(DumpWriter aWriter, const char * aIndent, const TLVReade temp.Init(aReader); tagControl = static_cast(temp.GetControlByte() & kTLVTagControlMask); - aWriter("%zd ", aDepth); + aWriter("0x%02X, ", temp.GetLengthRead()); for (size_t i = 0; i < aDepth; i++) aWriter("%s", aIndent); - aWriter("%p, ", temp.GetReadPoint()); - if (IsProfileTag(tag)) { aWriter("tag[%s]: 0x%x::0x%x::0x%x, ", DecodeTagControl(tagControl), VendorIdFromTag(tag), ProfileNumFromTag(tag), @@ -147,7 +145,11 @@ static void DumpHandler(DumpWriter aWriter, const char * aIndent, const TLVReade case kTLVType_ByteString: err = temp.GetDataPtr(strbuf); VerifyOrExit(err == CHIP_NO_ERROR, aWriter("Error in kTLVType_ByteString")); - aWriter("%p\n", strbuf); + aWriter("hex:"); + for (uint32_t i = 0; i < len; i++) + { + aWriter("%02X", strbuf[i]); + } break; case kTLVType_Null: @@ -267,7 +269,7 @@ const char * DecodeType(const TLVType aType) break; case kTLVType_ByteString: - retval = "Data"; + retval = "Octet String"; break; case kTLVType_Null: diff --git a/src/lib/core/tests/TestCHIPTLV.cpp b/src/lib/core/tests/TestCHIPTLV.cpp index 0e3e4d8f6fd59e..747aaff7d99a12 100644 --- a/src/lib/core/tests/TestCHIPTLV.cpp +++ b/src/lib/core/tests/TestCHIPTLV.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2020-2022 Project CHIP Authors * Copyright (c) 2013-2017 Nest Labs, Inc. * All rights reserved. * @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -1676,6 +1677,56 @@ void CheckPrettyPrinter(nlTestSuite * inSuite, void * inContext) chip::TLV::Debug::Dump(reader, SimpleDumpWriter); } +static char gStringDumpWriterBuf[128] = { 0 }; +static size_t gStringDumpWriterLengthWritten = 0; + +/** + * Log the specified message in the form of @a aFormat. + * + * @param[in] aFormat A pointer to a NULL-terminated C string with + * C Standard Library-style format specifiers + * containing the log message to be formatted and + * logged. + * @param[in] ... An argument list whose elements should correspond + * to the format specifiers in @a aFormat. + * + */ +void ENFORCE_FORMAT(1, 2) StringDumpWriter(const char * aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + + gStringDumpWriterLengthWritten += + static_cast(vsprintf(&gStringDumpWriterBuf[gStringDumpWriterLengthWritten], aFormat, args)); + + va_end(args); +} + +/** + * Test Octet String Pretty Printer + */ +void CheckOctetStringPrettyPrinter(nlTestSuite * inSuite, void * inContext) +{ + const uint8_t testOctetString[] = { 0x62, 0xFA, 0x82, 0x33, 0x59, 0xAC, 0xFA, 0xA9 }; + const char expectedPrint[] = + "0x04, tag[Common Profile (2 Bytes)]: 0x0::0x0::0x0, type: Octet String (0x10), length: 8, value: hex:62FA823359ACFAA9\n"; + uint8_t encodedBuf[128] = { 0 }; + + TLVWriter writer; + writer.Init(encodedBuf); + NL_TEST_ASSERT_SUCCESS(inSuite, writer.PutBytes(CommonTag(0), testOctetString, sizeof(testOctetString))); + NL_TEST_ASSERT_SUCCESS(inSuite, writer.Finalize()); + + TLVReader reader; + reader.Init(encodedBuf, writer.GetLengthWritten()); + + chip::TLV::Debug::Dump(reader, StringDumpWriter); + + NL_TEST_ASSERT(inSuite, strlen(expectedPrint) == strlen(gStringDumpWriterBuf)); + NL_TEST_ASSERT(inSuite, strcmp(expectedPrint, gStringDumpWriterBuf) == 0); +} + /** * Test Data Macros */ @@ -4397,6 +4448,7 @@ static const nlTest sTests[] = NL_TEST_DEF("Inet Buffer Test", CheckPacketBuffer), NL_TEST_DEF("Buffer Overflow Test", CheckBufferOverflow), NL_TEST_DEF("Pretty Print Test", CheckPrettyPrinter), + NL_TEST_DEF("Pretty Octet String Print Test", CheckOctetStringPrettyPrinter), NL_TEST_DEF("Data Macro Test", CheckDataMacro), NL_TEST_DEF("Strict Aliasing Test", CheckStrictAliasing), NL_TEST_DEF("CHIP TLV Basics", CheckCHIPTLVBasics), diff --git a/src/tools/chip-cert/BUILD.gn b/src/tools/chip-cert/BUILD.gn index dc7f6f49ab5294..0663e33c8ef7c6 100644 --- a/src/tools/chip-cert/BUILD.gn +++ b/src/tools/chip-cert/BUILD.gn @@ -26,6 +26,7 @@ executable("chip-cert") { "Cmd_GenAttCert.cpp", "Cmd_GenCD.cpp", "Cmd_GenCert.cpp", + "Cmd_PrintCD.cpp", "Cmd_PrintCert.cpp", "Cmd_ResignCert.cpp", "Cmd_ValidateAttCert.cpp", diff --git a/src/tools/chip-cert/Cmd_PrintCD.cpp b/src/tools/chip-cert/Cmd_PrintCD.cpp new file mode 100644 index 00000000000000..f5eca4c4c47c70 --- /dev/null +++ b/src/tools/chip-cert/Cmd_PrintCD.cpp @@ -0,0 +1,277 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file + * This file implements the command handler for the 'chip-cert' tool + * that prints the contents of a CHIP certificate. + * + */ + +#include "chip-cert.h" + +#include +#include +#include + +namespace { + +using namespace chip; +using namespace chip::ArgParser; +using namespace chip::Credentials; + +#define CMD_NAME "chip-cert print-cd" + +bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg); +bool HandleNonOptionArgs(const char * progName, int argc, char * const argv[]); + +// clang-format off +OptionDef gCmdOptionDefs[] = +{ + { "out", kArgumentRequired, 'o' }, + { } +}; + +const char * const gCmdOptionHelp = + " -o, --out \n" + "\n" + " The output printed CD content file name. If not specified\n" + " or if specified '-' then output is written to stdout.\n" + "\n" + ; + +OptionSet gCmdOptions = +{ + HandleOption, + gCmdOptionDefs, + "COMMAND OPTIONS", + gCmdOptionHelp +}; + +HelpOptions gHelpOptions( + CMD_NAME, + "Usage: " CMD_NAME " [] \n", + CHIP_VERSION_STRING "\n" COPYRIGHT_STRING, + "Print a CHIP certification declaration (CD) content.\n" + "\n" + "ARGUMENTS\n" + "\n" + " \n" + "\n" + " File or string containing a CHIP CMS Signed CD message.\n" + "\n" +); + +OptionSet *gCmdOptionSets[] = +{ + &gCmdOptions, + &gHelpOptions, + nullptr +}; +// clang-format on + +const char * gInFileNameOrStr = nullptr; +const char * gOutFileName = "-"; +FILE * gOutFile = nullptr; + +bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) +{ + switch (id) + { + case 'o': + gOutFileName = arg; + break; + default: + PrintArgError("%s: Unhandled option: %s\n", progName, name); + return false; + } + + return true; +} + +bool HandleNonOptionArgs(const char * progName, int argc, char * const argv[]) +{ + if (argc == 0) + { + PrintArgError("%s: Please specify the CD to be printed.\n", progName); + return false; + } + + if (argc > 1) + { + PrintArgError("%s: Unexpected argument: %s\n", progName, argv[1]); + return false; + } + + gInFileNameOrStr = argv[0]; + + return true; +} + +enum CDFormat +{ + kCDFormat_Unknown = 0, + kCDFormat_Raw, + kCDFormat_Hex, + kCDFormat_Base64, +}; + +CDFormat DetectCDFormat(const uint8_t * cd, uint32_t cdLen) +{ + static const uint8_t cdRawPrefix1[] = { 0x30, 0x81 }; + static const uint8_t cdRawPrefix2[] = { 0x30, 0x82 }; + static const char * cdHexPrefix = "308"; + static const char * cdB64Prefix = "MI"; + + VerifyOrReturnError(cd != nullptr, kCDFormat_Unknown); + + if ((cdLen > sizeof(cdRawPrefix1)) && + (memcmp(cd, cdRawPrefix1, sizeof(cdRawPrefix1)) == 0 || memcmp(cd, cdRawPrefix2, sizeof(cdRawPrefix2)) == 0)) + { + return kCDFormat_Raw; + } + + if ((cdLen > strlen(cdHexPrefix)) && (memcmp(cd, cdHexPrefix, strlen(cdHexPrefix)) == 0)) + { + return kCDFormat_Hex; + } + + if ((cdLen > strlen(cdB64Prefix)) && (memcmp(cd, cdB64Prefix, strlen(cdB64Prefix)) == 0)) + { + return kCDFormat_Base64; + } + + return kCDFormat_Unknown; +} + +bool ReadCD(const char * fileNameOrStr, MutableByteSpan cd) +{ + CDFormat cdFmt = kCDFormat_Unknown; + uint32_t cdLen = 0; + std::unique_ptr cdBuf; + + // If fileNameOrStr is a file name + if (access(fileNameOrStr, R_OK) == 0) + { + VerifyOrReturnError(ReadFileIntoMem(fileNameOrStr, nullptr, cdLen), false); + + cdBuf = std::unique_ptr(new uint8_t[cdLen]); + + VerifyOrReturnError(ReadFileIntoMem(fileNameOrStr, cdBuf.get(), cdLen), false); + + cdFmt = DetectCDFormat(cdBuf.get(), cdLen); + if (cdFmt == kCDFormat_Unknown) + { + fprintf(stderr, "Unrecognized CD Format in File: %s\n", fileNameOrStr); + return false; + } + } + // Otherwise, treat fileNameOrStr as a pointer to the CD string (in hex or base64 encoded format) + else + { + cdLen = static_cast(strlen(fileNameOrStr)); + + cdFmt = DetectCDFormat(reinterpret_cast(fileNameOrStr), cdLen); + if (cdFmt == kCDFormat_Unknown) + { + fprintf(stderr, "Unrecognized CD Format in the Input Argument: %s\n", fileNameOrStr); + return false; + } + + cdBuf = std::unique_ptr(new uint8_t[cdLen]); + memcpy(cdBuf.get(), fileNameOrStr, cdLen); + } + + if (cdFmt == kCDFormat_Hex) + { + size_t len = chip::Encoding::HexToBytes(Uint8::to_char(cdBuf.get()), cdLen, cdBuf.get(), cdLen); + VerifyOrReturnError(CanCastTo(2 * len), false); + VerifyOrReturnError(2 * len == cdLen, false); + cdLen = static_cast(len); + } + else if (cdFmt == kCDFormat_Base64) + { + VerifyOrReturnError(Base64Decode(cdBuf.get(), cdLen, cdBuf.get(), cdLen, cdLen), false); + } + + VerifyOrReturnError(cdLen <= cd.size(), false); + memcpy(cd.data(), cdBuf.get(), cdLen); + + cd.reduce_size(cdLen); + + return true; +} + +void ENFORCE_FORMAT(1, 2) SimpleDumpWriter(const char * aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + + vfprintf(gOutFile, aFormat, args); + + va_end(args); +} + +bool PrintCD(ByteSpan cd) +{ + chip::TLV::TLVReader reader; + ByteSpan signerKeyId; + ByteSpan cdContent; + std::unique_ptr signerKeyIdHex; + uint32_t signerKeyIdHexLen = 0; + + VerifyOrReturnError(!cd.empty(), false); + VerifyOrReturnError(OpenFile(gOutFileName, gOutFile, true), false); + VerifyOrReturnError(CMS_ExtractKeyId(cd, signerKeyId) == CHIP_NO_ERROR, false); + VerifyOrReturnError(CMS_ExtractCDContent(cd, cdContent) == CHIP_NO_ERROR, false); + + signerKeyIdHexLen = 2 * static_cast(signerKeyId.size()) + 1; + signerKeyIdHex = std::unique_ptr(new char[signerKeyIdHexLen]); + VerifyOrReturnError(Encoding::BytesToUppercaseHexString(signerKeyId.data(), signerKeyId.size(), signerKeyIdHex.get(), + signerKeyIdHexLen) == CHIP_NO_ERROR, + false); + + fprintf(gOutFile, "SignerKeyId value: hex:%s\n", signerKeyIdHex.get()); + + reader.Init(cdContent); + + VerifyOrReturnError(chip::TLV::Debug::Dump(reader, SimpleDumpWriter) == CHIP_NO_ERROR, false); + + return true; +} + +} // namespace + +bool Cmd_PrintCD(int argc, char * argv[]) +{ + uint8_t cdBuf[kCertificationElements_TLVEncodedMaxLength] = { 0 }; + MutableByteSpan cd(cdBuf); + + if (argc == 1) + { + gHelpOptions.PrintBriefUsage(stderr); + return true; + } + + VerifyOrReturnError(ParseArgs(CMD_NAME, argc, argv, gCmdOptionSets, HandleNonOptionArgs), false); + + VerifyOrReturnError(ReadCD(gInFileNameOrStr, cd), false); + + return PrintCD(cd); +} diff --git a/src/tools/chip-cert/README.md b/src/tools/chip-cert/README.md index 7161ac691db742..9b2b1fe9bda7ad 100644 --- a/src/tools/chip-cert/README.md +++ b/src/tools/chip-cert/README.md @@ -23,6 +23,8 @@ - [validate-att-cert](#validate-att-cert) - [gen-cd](#gen-cd) - [gen-cd example](#gen-cd-example) + - [print-cd](#print-cd) + - [print-cd example](#print-cd-example) - [version](#version) ## Introduction @@ -777,6 +779,45 @@ The binary output of the CMS signed CD is written to `cd.bin`. NOTE: `dac-origin-vendor-id` and `dac-origin-product-id` are not included in this example. +### print-cd + +``` +$ ./out/debug/standalone/chip-cert print-cd -h +Usage: chip-cert print-cd [] + +Print a CHIP certification declaration (CD) content. + +ARGUMENTS + + + + File or string containing a CHIP CMS Signed CD message. + +COMMAND OPTIONS + + -o, --out + + The output printed CD content file name. If not specified + or if specified '-' then output is written to stdout. + +HELP OPTIONS + + -h, --help + Print this output and then exit. + + -v, --version + Print the version and then exit. +``` + +#### gen-cd example + +An example of printing a Certificate Declaration (CD), which is provided as a +command line argument in a hex format: + +``` +./chip-cert print-cd 3081f506092a864886f70d010702a081e73081e4020103310d300b0609608648016503040201305006092a864886f70d010701a0430441152400012501f2ff360205018005028018250334122c04135a494732303134325a423333303030322d3234240500240600250794262408002509f1ff250a008018317e307c020103801462fa823359acfaa9963e1cfa140addf504f37160300b0609608648016503040201300a06082a8648ce3d04030204483046022100926296f7578158be7c459388336ca7383766c9eedd9855cbda6f4cf6bdf43211022100e0dbf4a2bcec4ea274baf0dea208b3365c6ed544086d101afdaf079a2c23e0de +``` + ### version Displays the version of the tool and copyright information. diff --git a/src/tools/chip-cert/chip-cert.cpp b/src/tools/chip-cert/chip-cert.cpp index 70b89a232e04b2..8bc63a011e53d0 100644 --- a/src/tools/chip-cert/chip-cert.cpp +++ b/src/tools/chip-cert/chip-cert.cpp @@ -64,6 +64,8 @@ const char * const sHelp = "\n" " gen-cd -- Generate a CHIP certification declaration signed message.\n" "\n" + " print-cd -- Print a CHIP certification declaration (CD) content.\n" + "\n" " version -- Print the program version and exit.\n" "\n"; // clang-format on @@ -137,6 +139,10 @@ extern "C" int main(int argc, char * argv[]) { res = Cmd_GenCD(argc - 1, argv + 1); } + else if (strcasecmp(argv[1], "print-cd") == 0 || strcasecmp(argv[1], "printcd") == 0) + { + res = Cmd_PrintCD(argc - 1, argv + 1); + } else { fprintf(stderr, "Unrecognized command: %s\n", argv[1]); diff --git a/src/tools/chip-cert/chip-cert.h b/src/tools/chip-cert/chip-cert.h index 2075285c6c7d8f..06ccd93693880f 100644 --- a/src/tools/chip-cert/chip-cert.h +++ b/src/tools/chip-cert/chip-cert.h @@ -398,6 +398,7 @@ extern bool Cmd_ResignCert(int argc, char * argv[]); extern bool Cmd_ValidateAttCert(int argc, char * argv[]); extern bool Cmd_ValidateCert(int argc, char * argv[]); extern bool Cmd_PrintCert(int argc, char * argv[]); +extern bool Cmd_PrintCD(int argc, char * argv[]); extern bool Cmd_GenAttCert(int argc, char * argv[]); extern bool ReadCert(const char * fileNameOrStr, X509 * cert);