diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001-8002-WithDACOrigin.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001-8002-WithDACOrigin.der new file mode 100644 index 00000000000000..592e313424b9cb Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001-8002-WithDACOrigin.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001-8002.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001-8002.der new file mode 100644 index 00000000000000..47a8df5479c67d Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001-8002.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der new file mode 100644 index 00000000000000..a3c84e7e877606 Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8002-WithDACOrigin.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8002-WithDACOrigin.der new file mode 100644 index 00000000000000..7239d6571749a1 Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8002-WithDACOrigin.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8002.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8002.der new file mode 100644 index 00000000000000..756e4952001d60 Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8002.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001-8002-WithDACOrigin.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001-8002-WithDACOrigin.der new file mode 100644 index 00000000000000..ab996e631f47aa Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001-8002-WithDACOrigin.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001-8002.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001-8002.der new file mode 100644 index 00000000000000..a2b7c28121200a Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001-8002.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001.der new file mode 100644 index 00000000000000..396ca754da3cc6 Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8001.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8002-WithDACOrigin.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8002-WithDACOrigin.der new file mode 100644 index 00000000000000..83b0b122428858 Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8002-WithDACOrigin.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8002.der b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8002.der new file mode 100644 index 00000000000000..2992009dad063b Binary files /dev/null and b/credentials/test/certification-declaration/Chip-Test-CD-FFF3-8002.der differ diff --git a/credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem b/credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem new file mode 100644 index 00000000000000..0392cd2b6a5dd6 --- /dev/null +++ b/credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBszCCAVqgAwIBAgIIRdrzneR6oI8wCgYIKoZIzj0EAwIwKzEpMCcGA1UEAwwg +TWF0dGVyIFRlc3QgQ0QgU2lnbmluZyBBdXRob3JpdHkwIBcNMjEwNjI4MTQyMzQz +WhgPOTk5OTEyMzEyMzU5NTlaMCsxKTAnBgNVBAMMIE1hdHRlciBUZXN0IENEIFNp +Z25pbmcgQXV0aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPDmJIkUr +VcrzicJb0bykZWlSzLkOiGkkmthHRlMBTL+V1oeWXgNrUhxRA35rjO3vyh60QEZp +T6CIgu7WUZ3suqNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFGL6gjNZrPqplj4c+hQK3fUE83FgMB8GA1UdIwQYMBaAFGL6 +gjNZrPqplj4c+hQK3fUE83FgMAoGCCqGSM49BAMCA0cAMEQCICxUXOTkV9im8NnZ +u+vW7OHd/n+MbZps83UyH8b6xxOEAiBUB3jodDlyUn7t669YaGIgtUB48s1OYqdq +58u5L/VMiw== +-----END CERTIFICATE----- diff --git a/credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem b/credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem new file mode 100644 index 00000000000000..fcf844d3ee0920 --- /dev/null +++ b/credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIK7zSEEW6UgexXvgRy30G/SZBk5QJK2GnspeiJgC1IB1oAoGCCqGSM49 +AwEHoUQDQgAEPDmJIkUrVcrzicJb0bykZWlSzLkOiGkkmthHRlMBTL+V1oeWXgNr +UhxRA35rjO3vyh60QEZpT6CIgu7WUZ3sug== +-----END EC PRIVATE KEY----- diff --git a/credentials/test/gen-test-cds.sh b/credentials/test/gen-test-cds.sh new file mode 100755 index 00000000000000..79264da118c512 --- /dev/null +++ b/credentials/test/gen-test-cds.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +# +# Copyright (c) 2021 Project CHIP Authors +# +# 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. +# + +# Script that can be used to generate Certification Declaration (CD) +# for testing purposes. +# The script expects the path to the chip-cert tool binary as an input argument. +# +# Usage example when the script is run from the CHIP SDK root directory: +# ./credentials/test/gen-test-cds.sh ./out/debug/standalone/chip-cert +# +# The result will be stored in: +# credentials/test/certification-declaration +# +# If the intention is to re-generate a new set of CDs that replace the already +# present ones then it is recommended to clear the folder: +# rm credentials/test/certification-declaration/* +# + +set -e + +here=${0%/*} + +dest_dir="$here/certification-declaration" + +mkdir -p "$dest_dir" + +if [ $# == 1 ]; then + chip_cert_tool=$1 +else + echo "Error: Please specify exactly one input argument; the path to the chip-cert tool binary" + exit +fi + +cert_valid_from="2021-06-28 14:23:43" +cert_lifetime=4294967295 + +format_version=1 +vids=(FFF2 FFF3) +pid0=8001 +pid1=8002 +device_type_id=0x1234 +certificate_id0="ZIG20141ZB330001-24" +certificate_id1="ZIG20142ZB330002-24" +security_level=0 +security_info=0 +version_num=9876 +certification_type=0 +dac_origin_vendor_id=0xFFF1 +dac_origin_product_id=0x8000 + +cd_signing_key="$dest_dir/Chip-Test-CD-Signing-Key.pem" +cd_signing_cert="$dest_dir/Chip-Test-CD-Signing-Cert.pem" + +# Using gen-att-cert command to generate CD Signing Cert/Key: +"$chip_cert_tool" gen-att-cert --type a --subject-cn "Matter Test CD Signing Authority" --valid-from "$cert_valid_from" --lifetime "$cert_lifetime" --out-key "$cd_signing_key" --out "$cd_signing_cert" + +for vid in "${vids[@]}"; do + + "$chip_cert_tool" gen-cd --key "$cd_signing_key" --cert "$cd_signing_cert" --out "$dest_dir/Chip-Test-CD-$vid-$pid0.der" --format-version "$format_version" --vendor-id "0x$vid" --product-id "0x$pid0" --device-type-id "$device_type_id" --certificate-id "$certificate_id0" --security-level "$security_level" --security-info "$security_info" --version-number "$version_num" --certification-type "$certification_type" + + "$chip_cert_tool" gen-cd --key "$cd_signing_key" --cert "$cd_signing_cert" --out "$dest_dir/Chip-Test-CD-$vid-$pid1.der" --format-version "$format_version" --vendor-id "0x$vid" --product-id "0x$pid1" --device-type-id "$device_type_id" --certificate-id "$certificate_id1" --security-level "$security_level" --security-info "$security_info" --version-number "$version_num" --certification-type "$certification_type" + + "$chip_cert_tool" gen-cd --key "$cd_signing_key" --cert "$cd_signing_cert" --out "$dest_dir/Chip-Test-CD-$vid-$pid1-WithDACOrigin.der" --format-version "$format_version" --vendor-id "0x$vid" --product-id "0x$pid1" --device-type-id "$device_type_id" --certificate-id "$certificate_id1" --security-level "$security_level" --security-info "$security_info" --version-number "$version_num" --certification-type "$certification_type" --dac-origin-vendor-id "$dac_origin_vendor_id" --dac-origin-product-id "$dac_origin_product_id" + + "$chip_cert_tool" gen-cd --key "$cd_signing_key" --cert "$cd_signing_cert" --out "$dest_dir/Chip-Test-CD-$vid-$pid0-$pid1.der" --format-version "$format_version" --vendor-id "0x$vid" --product-id "0x$pid0" --product-id "0x$pid1" --device-type-id "$device_type_id" --certificate-id "$certificate_id1" --security-level "$security_level" --security-info "$security_info" --version-number "$version_num" --certification-type "$certification_type" + + "$chip_cert_tool" gen-cd --key "$cd_signing_key" --cert "$cd_signing_cert" --out "$dest_dir/Chip-Test-CD-$vid-$pid0-$pid1-WithDACOrigin.der" --format-version "$format_version" --vendor-id "0x$vid" --product-id "0x$pid0" --product-id "0x$pid1" --device-type-id "$device_type_id" --certificate-id "$certificate_id1" --security-level "$security_level" --security-info "$security_info" --version-number "$version_num" --certification-type "$certification_type" --dac-origin-vendor-id "$dac_origin_vendor_id" --dac-origin-product-id "$dac_origin_product_id" + +done diff --git a/src/lib/support/CHIPArgParser.cpp b/src/lib/support/CHIPArgParser.cpp index 2f0d0d74bb4acc..7618bcdcdfae87 100644 --- a/src/lib/support/CHIPArgParser.cpp +++ b/src/lib/support/CHIPArgParser.cpp @@ -753,6 +753,64 @@ bool ParseInt(const char * str, int32_t & output, int base) return parseEnd > str && *parseEnd == 0 && ((v != LONG_MIN && v != LONG_MAX) || errno == 0); } +/** + * Parse and attempt to convert a string to a 16-bit unsigned integer, + * applying the appropriate interpretation based on the base parameter. + * + * @param[in] str A pointer to a NULL-terminated C string representing + * the integer to parse. + * @param[out] output A reference to storage for a 16-bit unsigned integer + * to which the parsed value will be stored on success. + * @param[in] base The base according to which the string should be + * interpreted and parsed. If 0 or 16, the string may + * be hexadecimal and prefixed with "0x". Otherwise, a 0 + * is implied as 10 unless a leading 0 is encountered in + * which 8 is implied. + * + * @return true on success; otherwise, false on failure. + */ +bool ParseInt(const char * str, uint16_t & output, int base) +{ + uint32_t v; + + if (!ParseInt(str, v, base) || !CanCastTo(v)) + { + return false; + } + output = static_cast(v); + + return true; +} + +/** + * Parse and attempt to convert a string to a 8-bit unsigned integer, + * applying the appropriate interpretation based on the base parameter. + * + * @param[in] str A pointer to a NULL-terminated C string representing + * the integer to parse. + * @param[out] output A reference to storage for a 8-bit unsigned integer + * to which the parsed value will be stored on success. + * @param[in] base The base according to which the string should be + * interpreted and parsed. If 0 or 16, the string may + * be hexadecimal and prefixed with "0x". Otherwise, a 0 + * is implied as 10 unless a leading 0 is encountered in + * which 8 is implied. + * + * @return true on success; otherwise, false on failure. + */ +bool ParseInt(const char * str, uint8_t & output, int base) +{ + uint32_t v; + + if (!ParseInt(str, v, base) || !CanCastTo(v)) + { + return false; + } + output = static_cast(v); + + return true; +} + /** * Parse and attempt to convert a string interpreted as a decimal * value to a 64-bit unsigned integer, applying the appropriate diff --git a/src/lib/support/CHIPArgParser.hpp b/src/lib/support/CHIPArgParser.hpp index c87a0caec0a93f..daeab0be9e6729 100644 --- a/src/lib/support/CHIPArgParser.hpp +++ b/src/lib/support/CHIPArgParser.hpp @@ -123,6 +123,8 @@ bool ParseInt(const char * str, uint16_t & output); bool ParseInt(const char * str, int32_t & output); bool ParseInt(const char * str, uint32_t & output); bool ParseInt(const char * str, uint64_t & output); +bool ParseInt(const char * str, uint8_t & output, int base); +bool ParseInt(const char * str, uint16_t & output, int base); bool ParseInt(const char * str, int32_t & output, int base); bool ParseInt(const char * str, uint32_t & output, int base); bool ParseInt(const char * str, uint64_t & output, int base); diff --git a/src/tools/chip-cert/BUILD.gn b/src/tools/chip-cert/BUILD.gn index 8b5c0253ed9431..0a7dc172c585e2 100644 --- a/src/tools/chip-cert/BUILD.gn +++ b/src/tools/chip-cert/BUILD.gn @@ -24,6 +24,7 @@ executable("chip-cert") { "Cmd_ConvertCert.cpp", "Cmd_ConvertKey.cpp", "Cmd_GenAttCert.cpp", + "Cmd_GenCD.cpp", "Cmd_GenCert.cpp", "Cmd_PrintCert.cpp", "Cmd_ResignCert.cpp", diff --git a/src/tools/chip-cert/CertUtils.cpp b/src/tools/chip-cert/CertUtils.cpp index 120a4a0bde204c..440531d18d30f8 100644 --- a/src/tools/chip-cert/CertUtils.cpp +++ b/src/tools/chip-cert/CertUtils.cpp @@ -166,7 +166,7 @@ namespace { CertFormat DetectCertFormat(uint8_t * cert, uint32_t certLen) { static const uint8_t chipRawPrefix[] = { 0x15, 0x30, 0x01 }; - static const char * chipB64Prefix = "FTABC"; + static const char * chipB64Prefix = "FTAB"; static const size_t chipB64PrefixLen = strlen(chipB64Prefix); static const char * pemMarker = "-----BEGIN CERTIFICATE-----"; diff --git a/src/tools/chip-cert/Cmd_GenAttCert.cpp b/src/tools/chip-cert/Cmd_GenAttCert.cpp index 0c7f12b4191764..c01769d1f71064 100644 --- a/src/tools/chip-cert/Cmd_GenAttCert.cpp +++ b/src/tools/chip-cert/Cmd_GenAttCert.cpp @@ -150,8 +150,6 @@ struct tm gValidFrom; bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) { - uint64_t chip64bitAttr; - switch (id) { case 't': @@ -181,20 +179,18 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char gSubjectCN = arg; break; case 'V': - if (!ParseChip64bitAttr(arg, chip64bitAttr) || !chip::CanCastTo(chip64bitAttr)) + if (!ParseInt(arg, gSubjectVID, 16)) { PrintArgError("%s: Invalid value specified for the subject VID attribute: %s\n", progName, arg); return false; } - gSubjectVID = static_cast(chip64bitAttr); break; case 'P': - if (!ParseChip64bitAttr(arg, chip64bitAttr) || !chip::CanCastTo(chip64bitAttr)) + if (!ParseInt(arg, gSubjectPID, 16)) { PrintArgError("%s: Invalid value specified for the subject PID attribute: %s\n", progName, arg); return false; } - gSubjectPID = static_cast(chip64bitAttr); break; case 'k': gInKeyFileName = arg; diff --git a/src/tools/chip-cert/Cmd_GenCD.cpp b/src/tools/chip-cert/Cmd_GenCD.cpp new file mode 100644 index 00000000000000..4509a7f8b2e2b8 --- /dev/null +++ b/src/tools/chip-cert/Cmd_GenCD.cpp @@ -0,0 +1,367 @@ +/* + * + * Copyright (c) 2021 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 generates a CHIP Certification Declaration. + * + */ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include "chip-cert.h" + +#include + +namespace { + +using namespace chip; +using namespace chip::ArgParser; +using namespace chip::Credentials; +using namespace chip::Crypto; + +#define CMD_NAME "chip-cert gen-cd" + +bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg); + +// clang-format off +OptionDef gCmdOptionDefs[] = +{ + { "key", kArgumentRequired, 'K' }, + { "cert", kArgumentRequired, 'C' }, + { "out", kArgumentRequired, 'O' }, + { "format-version", kArgumentRequired, 'f' }, + { "vendor-id", kArgumentRequired, 'V' }, + { "product-id", kArgumentRequired, 'p' }, + { "device-type-id", kArgumentRequired, 'd' }, + { "certificate-id", kArgumentRequired, 'c' }, + { "security-level", kArgumentRequired, 'l' }, + { "security-info", kArgumentRequired, 'i' }, + { "version-number", kArgumentRequired, 'n' }, + { "certification-type", kArgumentRequired, 't' }, + { "dac-origin-vendor-id", kArgumentRequired, 'o' }, + { "dac-origin-product-id", kArgumentRequired, 'r' }, + { } +}; + +const char * const gCmdOptionHelp = + " -K, --key \n" + "\n" + " File containing private key to be used to sign the Certification Declaration.\n" + "\n" + " -C, --cert \n" + "\n" + " File containing certificate associated with the private key that is used\n" + " to sign the Certification Declaration. The Subject Key Identifier in the\n" + " certificate will be included in the signed Certification Declaration message.\n" + "\n" + " -O, --out \n" + "\n" + " File to contain the signed Certification Declaration message.\n" + "\n" + " -f, --format-version \n" + "\n" + " Format Version.\n" + "\n" + " -V, --vendor-id \n" + "\n" + " Vendor Id (VID) in hex.\n" + "\n" + " -p, --product-id \n" + "\n" + " Product Id (PID) in hex. Maximum 100 PID values can be specified.\n" + " Each PID value should have it's own -p or --product-id option selector.\n" + "\n" + " -d, --device-type-id \n" + "\n" + " Device Type Id in hex.\n" + "\n" + " -c, --certificate-id \n" + "\n" + " Certificate Id encoded as UTF8 string.\n" + "\n" + " -l, --security-level \n" + "\n" + " Security Level in hex.\n" + "\n" + " -i, --security-info \n" + "\n" + " Security Information in hex.\n" + "\n" + " -n, --version-number \n" + "\n" + " Version Number in hex.\n" + "\n" + " -t, --certification-type \n" + "\n" + " Certification Type. Valid values are:\n" + " 0 - Development and Test (default)\n" + " 1 - Provisional\n" + " 2 - Official\n" + "\n" + " -o, --dac-origin-vendor-id \n" + "\n" + " DAC Origin Vendor Id in hex.\n" + "\n" + " -r, --dac-origin-product-id \n" + "\n" + " DAC Origin Product Id in hex.\n" + "\n" + ; + +OptionSet gCmdOptions = +{ + HandleOption, + gCmdOptionDefs, + "COMMAND OPTIONS", + gCmdOptionHelp +}; + +HelpOptions gHelpOptions( + CMD_NAME, + "Usage: " CMD_NAME " [ ]\n", + CHIP_VERSION_STRING "\n" COPYRIGHT_STRING, + "Generate CD CMS Signed Message" +); + +OptionSet *gCmdOptionSets[] = +{ + &gCmdOptions, + &gHelpOptions, + nullptr +}; +// clang-format on + +CertificationElements gCertElements = { 0 }; +const char * gCertFileName = nullptr; +const char * gKeyFileName = nullptr; +const char * gSignedCDFileName = nullptr; + +bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) +{ + switch (id) + { + case 'C': + gCertFileName = arg; + break; + case 'K': + gKeyFileName = arg; + break; + case 'O': + gSignedCDFileName = arg; + break; + case 'f': + if (!ParseInt(arg, gCertElements.FormatVersion, 16)) + { + PrintArgError("%s: Invalid value specified for Format Version: %s\n", progName, arg); + return false; + } + break; + case 'V': + if (!ParseInt(arg, gCertElements.VendorId, 16) || gCertElements.VendorId == 0) + { + PrintArgError("%s: Invalid value specified for Vendor Id: %s\n", progName, arg); + return false; + } + break; + case 'p': + if (gCertElements.ProductIdsCount == ArraySize(gCertElements.ProductIds)) + { + PrintArgError("%s: Too many Product Ids are specified: %s\n", progName, arg); + return false; + } + if (!ParseInt(arg, gCertElements.ProductIds[gCertElements.ProductIdsCount], 16) || + gCertElements.ProductIds[gCertElements.ProductIdsCount] == 0) + { + PrintArgError("%s: Invalid value specified for Product Id: %s\n", progName, arg); + return false; + } + gCertElements.ProductIdsCount++; + break; + case 'd': + if (!ParseInt(arg, gCertElements.DeviceTypeId, 16)) + { + PrintArgError("%s: Invalid value specified for Device Type Id: %s\n", progName, arg); + return false; + } + break; + case 'c': + if (strlen(arg) != kCertificateIdLength) + { + PrintArgError("%s: Invalid value specified for Certificate Id: %s\n", progName, arg); + return false; + } + memcpy(gCertElements.CertificateId, arg, strlen(arg)); + gCertElements.CertificateId[kCertificateIdLength] = '\0'; + break; + case 'l': + if (!ParseInt(arg, gCertElements.SecurityLevel, 16)) + { + PrintArgError("%s: Invalid value specified for Security Level: %s\n", progName, arg); + return false; + } + break; + case 'i': + if (!ParseInt(arg, gCertElements.SecurityInformation, 16)) + { + PrintArgError("%s: Invalid value specified for Security Information: %s\n", progName, arg); + return false; + } + break; + case 'n': + if (!ParseInt(arg, gCertElements.VersionNumber, 16)) + { + PrintArgError("%s: Invalid value specified for Version Number: %s\n", progName, arg); + return false; + } + break; + case 't': + if (!ParseInt(arg, gCertElements.CertificationType) || gCertElements.CertificationType > 2) + { + PrintArgError("%s: Invalid value specified for Certification Type: %s\n", progName, arg); + return false; + } + break; + case 'o': + if (!ParseInt(arg, gCertElements.DACOriginVendorId, 16) || gCertElements.DACOriginVendorId == 0) + { + PrintArgError("%s: Invalid value specified for DAC Origin Vendor Id: %s\n", progName, arg); + return false; + } + gCertElements.DACOriginVIDandPIDPresent = true; + break; + case 'r': + if (!ParseInt(arg, gCertElements.DACOriginProductId, 16) || gCertElements.DACOriginProductId == 0) + { + PrintArgError("%s: Invalid value specified for DAC Origin Product Id: %s\n", progName, arg); + return false; + } + gCertElements.DACOriginVIDandPIDPresent = true; + break; + default: + PrintArgError("%s: Unhandled option: %s\n", progName, name); + return false; + } + + return true; +} + +} // namespace + +bool Cmd_GenCD(int argc, char * argv[]) +{ + if (argc == 1) + { + gHelpOptions.PrintBriefUsage(stderr); + return true; + } + + VerifyOrReturnError(ParseArgs(CMD_NAME, argc, argv, gCmdOptionSets), false); + + if (gKeyFileName == nullptr) + { + fprintf(stderr, "Please specify the signing private key file name using the --key option.\n"); + return false; + } + + if (gCertFileName == nullptr) + { + fprintf(stderr, "Please specify the signing certificate file name using the --cert option.\n"); + return false; + } + + if (gSignedCDFileName == nullptr) + { + fprintf(stderr, "Please specify the file name for the signed Certification Declaration using the --out option.\n"); + return false; + } + + if (gCertElements.VendorId == 0 || gCertElements.ProductIdsCount == 0 || gCertElements.DeviceTypeId == 0 || + strlen(gCertElements.CertificateId) == 0 || gCertElements.VersionNumber == 0) + { + fprintf(stderr, "Please specify all mandatory CD elements.\n"); + return false; + } + + if (gCertElements.DACOriginVIDandPIDPresent && (gCertElements.DACOriginVendorId == 0 || gCertElements.DACOriginProductId == 0)) + { + fprintf(stderr, "The DAC Origin Vendor Id and Product Id SHALL be specified together.\n"); + return false; + } + + if (strcmp(gSignedCDFileName, "-") != 0 && access(gSignedCDFileName, R_OK) == 0) + { + fprintf(stderr, + "Output signed CD file already exists (%s)\n" + "To replace the file, please remove it and re-run the command.\n", + gSignedCDFileName); + return false; + } + + { + std::unique_ptr cert(X509_new(), &X509_free); + std::unique_ptr key(EVP_PKEY_new(), &EVP_PKEY_free); + + VerifyOrReturnError(ReadCert(gCertFileName, cert.get()), false); + VerifyOrReturnError(ReadKey(gKeyFileName, key.get()), false); + + // Extract the subject key id from the X509 certificate. + ByteSpan signerKeyId; + { + const ASN1_OCTET_STRING * skidString = X509_get0_subject_key_id(cert.get()); + VerifyOrReturnError(skidString != nullptr, false); + VerifyOrReturnError(CanCastTo(skidString->length), false); + signerKeyId = ByteSpan(skidString->data, static_cast(skidString->length)); + } + + // Initialize P256Keypair from EVP_PKEY. + P256Keypair keypair; + { + P256SerializedKeypair serializedKeypair; + VerifyOrReturnError(SerializeKeyPair(key.get(), serializedKeypair), false); + VerifyOrReturnError(keypair.Deserialize(serializedKeypair) == CHIP_NO_ERROR, false); + } + + // Encode CD TLV content. + uint8_t encodedCDBuf[kCertificationElements_TLVEncodedMaxLength]; + MutableByteSpan encodedCD(encodedCDBuf); + VerifyOrReturnError(EncodeCertificationElements(gCertElements, encodedCD) == CHIP_NO_ERROR, false); + + // Sign CD. + uint8_t signedMessageBuf[kMaxCMSSignedCDMessage]; + MutableByteSpan signedMessage(signedMessageBuf); + VerifyOrReturnError(CMS_Sign(encodedCD, signerKeyId, keypair, signedMessage) == CHIP_NO_ERROR, false); + + // Write to file. + { + FILE * file = nullptr; + + VerifyOrReturnError(OpenFile(gSignedCDFileName, file, true), false); + + if (fwrite(signedMessage.data(), 1, signedMessage.size(), file) != signedMessage.size()) + { + fprintf(stderr, "Unable to write to %s: %s\n", gSignedCDFileName, strerror(ferror(file) ? errno : ENOSPC)); + return false; + } + } + } + return true; +} diff --git a/src/tools/chip-cert/Cmd_GenCert.cpp b/src/tools/chip-cert/Cmd_GenCert.cpp index 9a459f346685af..7eaba4d87749e9 100644 --- a/src/tools/chip-cert/Cmd_GenCert.cpp +++ b/src/tools/chip-cert/Cmd_GenCert.cpp @@ -192,6 +192,7 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char { CHIP_ERROR err = CHIP_NO_ERROR; uint64_t chip64bitAttr; + uint32_t chip32bitAttr; OID attrOID; switch (id) @@ -226,7 +227,7 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char break; case 'i': - if (!ParseChip64bitAttr(arg, chip64bitAttr)) + if (!ParseInt(arg, chip64bitAttr, 16)) { PrintArgError("%s: Invalid value specified for subject chip id attribute: %s\n", progName, arg); return false; @@ -260,7 +261,7 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char break; case 'a': - if (!ParseChip64bitAttr(arg, chip64bitAttr)) + if (!ParseInt(arg, chip32bitAttr, 16)) { PrintArgError("%s: Invalid value specified for the subject authentication tag attribute: %s\n", progName, arg); return false; @@ -280,7 +281,7 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char return false; } - err = gSubjectDN.AddAttribute(attrOID, chip64bitAttr); + err = gSubjectDN.AddAttribute(attrOID, chip32bitAttr); if (err != CHIP_NO_ERROR) { fprintf(stderr, "Failed to add subject DN attribute: %s\n", chip::ErrorStr(err)); @@ -296,7 +297,7 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char } break; case 'f': - if (!ParseChip64bitAttr(arg, chip64bitAttr)) + if (!ParseInt(arg, chip64bitAttr, 16)) { PrintArgError("%s: Invalid value specified for subject fabric id attribute: %s\n", progName, arg); return false; diff --git a/src/tools/chip-cert/GeneralUtils.cpp b/src/tools/chip-cert/GeneralUtils.cpp index 8641a6e9c59dde..23adb060aca962 100644 --- a/src/tools/chip-cert/GeneralUtils.cpp +++ b/src/tools/chip-cert/GeneralUtils.cpp @@ -177,15 +177,6 @@ bool ContainsPEMMarker(const char * marker, const uint8_t * data, uint32_t dataL return false; } -bool ParseChip64bitAttr(const char * str, uint64_t & val) -{ - char * parseEnd; - - errno = 0; - val = strtoull(str, &parseEnd, 16); - return parseEnd > str && *parseEnd == 0 && (val != ULLONG_MAX || errno == 0); -} - bool ParseDateTime(const char * str, struct tm & date) { const char * p; diff --git a/src/tools/chip-cert/KeyUtils.cpp b/src/tools/chip-cert/KeyUtils.cpp index 01a76975da6a73..c41d31c7095e3d 100644 --- a/src/tools/chip-cert/KeyUtils.cpp +++ b/src/tools/chip-cert/KeyUtils.cpp @@ -66,45 +66,6 @@ KeyFormat DetectKeyFormat(const uint8_t * key, uint32_t keyLen) return kKeyFormat_X509_DER; } -bool SerializeKeyPair(EVP_PKEY * key, uint8_t * chipKey, uint32_t chipKeyBufSize, uint32_t & chipKeyLen) -{ - bool res = true; - const BIGNUM * privKeyBN = nullptr; - const EC_GROUP * group = nullptr; - const EC_KEY * ecKey = nullptr; - const EC_POINT * ecPoint = nullptr; - uint8_t * pubKey = chipKey; - uint8_t * privKey = chipKey + kP256_PublicKey_Length; - size_t pubKeyLen = 0; - int privKeyLen = 0; - - VerifyOrExit(chipKeyBufSize >= kP256_PublicKey_Length + kP256_PrivateKey_Length, res = false); - - ecKey = EVP_PKEY_get1_EC_KEY(key); - VerifyOrExit(ecKey != nullptr, res = false); - - privKeyBN = EC_KEY_get0_private_key(ecKey); - VerifyOrExit(privKeyBN != nullptr, res = false); - - group = EC_KEY_get0_group(ecKey); - VerifyOrExit(group != nullptr, res = false); - - ecPoint = EC_KEY_get0_public_key(ecKey); - VerifyOrExit(ecPoint != nullptr, res = false); - - pubKeyLen = - EC_POINT_point2oct(group, ecPoint, POINT_CONVERSION_UNCOMPRESSED, Uint8::to_uchar(pubKey), kP256_PublicKey_Length, nullptr); - VerifyOrExit(pubKeyLen == kP256_PublicKey_Length, res = false); - - privKeyLen = BN_bn2binpad(privKeyBN, privKey, kP256_PrivateKey_Length); - VerifyOrExit(privKeyLen == kP256_PrivateKey_Length, res = false); - - chipKeyLen = kP256_PublicKey_Length + kP256_PrivateKey_Length; - -exit: - return res; -} - bool DeserializeKeyPair(const uint8_t * keyPair, uint32_t keyPairLen, EVP_PKEY * key) { bool res = true; @@ -144,6 +105,43 @@ bool DeserializeKeyPair(const uint8_t * keyPair, uint32_t keyPairLen, EVP_PKEY * } // namespace +bool SerializeKeyPair(EVP_PKEY * key, P256SerializedKeypair & serializedKeypair) +{ + bool res = true; + const BIGNUM * privKeyBN = nullptr; + const EC_GROUP * group = nullptr; + const EC_KEY * ecKey = nullptr; + const EC_POINT * ecPoint = nullptr; + uint8_t * pubKey = serializedKeypair; + uint8_t * privKey = pubKey + kP256_PublicKey_Length; + size_t pubKeyLen = 0; + int privKeyLen = 0; + + ecKey = EVP_PKEY_get1_EC_KEY(key); + VerifyOrExit(ecKey != nullptr, res = false); + + privKeyBN = EC_KEY_get0_private_key(ecKey); + VerifyOrExit(privKeyBN != nullptr, res = false); + + group = EC_KEY_get0_group(ecKey); + VerifyOrExit(group != nullptr, res = false); + + ecPoint = EC_KEY_get0_public_key(ecKey); + VerifyOrExit(ecPoint != nullptr, res = false); + + pubKeyLen = + EC_POINT_point2oct(group, ecPoint, POINT_CONVERSION_UNCOMPRESSED, Uint8::to_uchar(pubKey), kP256_PublicKey_Length, nullptr); + VerifyOrExit(pubKeyLen == kP256_PublicKey_Length, res = false); + + privKeyLen = BN_bn2binpad(privKeyBN, privKey, kP256_PrivateKey_Length); + VerifyOrExit(privKeyLen == kP256_PrivateKey_Length, res = false); + + serializedKeypair.SetLength(kP256_PublicKey_Length + kP256_PrivateKey_Length); + +exit: + return res; +} + bool ReadKey(const char * fileName, EVP_PKEY * key) { bool res = true; @@ -250,6 +248,7 @@ bool WritePrivateKey(const char * fileName, EVP_PKEY * key, KeyFormat keyFmt) uint32_t chipKeyBase64Len = BASE64_ENCODED_LEN(chipKeyLen); std::unique_ptr chipKey(new uint8_t[chipKeyLen]); std::unique_ptr chipKeyBase64(new uint8_t[chipKeyBase64Len]); + P256SerializedKeypair serializedKeypair; VerifyOrExit(key != nullptr, res = false); @@ -278,12 +277,13 @@ bool WritePrivateKey(const char * fileName, EVP_PKEY * key, KeyFormat keyFmt) break; case kKeyFormat_Chip_Raw: case kKeyFormat_Chip_Base64: - res = SerializeKeyPair(key, chipKey.get(), chipKeyLen, chipKeyLen); + res = SerializeKeyPair(key, serializedKeypair); VerifyTrueOrExit(res); if (keyFmt == kKeyFormat_Chip_Base64) { - res = Base64Encode(chipKey.get(), chipKeyLen, chipKeyBase64.get(), chipKeyBase64Len, chipKeyBase64Len); + res = Base64Encode(serializedKeypair, static_cast(serializedKeypair.Length()), chipKeyBase64.get(), + chipKeyBase64Len, chipKeyBase64Len); VerifyTrueOrExit(res); keyToWrite = chipKeyBase64.get(); diff --git a/src/tools/chip-cert/chip-cert.cpp b/src/tools/chip-cert/chip-cert.cpp index 9d25369b728794..91a90273d249b5 100644 --- a/src/tools/chip-cert/chip-cert.cpp +++ b/src/tools/chip-cert/chip-cert.cpp @@ -58,6 +58,10 @@ const char * const sHelp = "\n" " print-cert -- Print a CHIP certificate.\n" "\n" + " gen-att-cert -- Generate a CHIP attestation certificate.\n" + "\n" + " gen-cd -- Generate a CHIP certification declaration signed message.\n" + "\n" " version -- Print the program version and exit.\n" "\n"; // clang-format on @@ -123,6 +127,10 @@ extern "C" int main(int argc, char * argv[]) { res = Cmd_GenAttCert(argc - 1, argv + 1); } + else if (strcasecmp(argv[1], "gen-cd") == 0 || strcasecmp(argv[1], "gencd") == 0) + { + res = Cmd_GenCD(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 77c2042f3ed6d3..66e395993e539e 100644 --- a/src/tools/chip-cert/chip-cert.h +++ b/src/tools/chip-cert/chip-cert.h @@ -51,6 +51,7 @@ #include #include +#include #include #include #include @@ -117,6 +118,7 @@ class ToolChipDN : public chip::Credentials::ChipDN void PrintDN(FILE * file, const char * name) const; }; +extern bool Cmd_GenCD(int argc, char * argv[]); extern bool Cmd_GenCert(int argc, char * argv[]); extern bool Cmd_ConvertCert(int argc, char * argv[]); extern bool Cmd_ConvertKey(int argc, char * argv[]); @@ -143,6 +145,7 @@ extern bool MakeAttCert(AttCertType attCertType, const char * subjectCN, uint16_ extern bool GenerateKeyPair(EVP_PKEY * key); extern bool ReadKey(const char * fileName, EVP_PKEY * key); extern bool WritePrivateKey(const char * fileName, EVP_PKEY * key, KeyFormat keyFmt); +extern bool SerializeKeyPair(EVP_PKEY * key, chip::Crypto::P256SerializedKeypair & serializedKeypair); extern bool X509ToChipCert(X509 * cert, chip::MutableByteSpan & chipCert); @@ -151,7 +154,6 @@ extern bool Base64Encode(const uint8_t * inData, uint32_t inDataLen, uint8_t * o extern bool Base64Decode(const uint8_t * inData, uint32_t inDataLen, uint8_t * outBuf, uint32_t outBufSize, uint32_t & outDataLen); extern bool IsBase64String(const char * str, uint32_t strLen); extern bool ContainsPEMMarker(const char * marker, const uint8_t * data, uint32_t dataLen); -extern bool ParseChip64bitAttr(const char * str, uint64_t & val); extern bool ParseDateTime(const char * str, struct tm & date); extern bool ReadFileIntoMem(const char * fileName, uint8_t * data, uint32_t & dataLen); extern bool OpenFile(const char * fileName, FILE *& file, bool toWrite = false);