| /* | 
 |  * | 
 |  *    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 validates a CHIP attestation certificate chain. | 
 |  * | 
 |  */ | 
 |  | 
 | #include "chip-cert.h" | 
 |  | 
 | #include <credentials/attestation_verifier/DeviceAttestationVerifier.h> | 
 |  | 
 | namespace { | 
 |  | 
 | using namespace chip; | 
 | using namespace chip::ArgParser; | 
 | using namespace chip::Credentials; | 
 | using namespace chip::Crypto; | 
 | using namespace chip::ASN1; | 
 |  | 
 | #define CMD_NAME "chip-cert validate-att-cert" | 
 |  | 
 | bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg); | 
 |  | 
 | // clang-format off | 
 | OptionDef gCmdOptionDefs[] = | 
 | { | 
 |     { "dac",            kArgumentRequired,  'd' }, | 
 |     { "pai",            kArgumentRequired,  'i' }, | 
 |     { "paa",            kArgumentRequired,  'a' }, | 
 |     { } | 
 | }; | 
 |  | 
 | const char * const gCmdOptionHelp = | 
 |     "  -d, --dac <file/str>\n" | 
 |     "\n" | 
 |     "       File or string containing Device Attestation Certificate (DAC) to be validated.\n" | 
 |     "       The DAC format is auto-detected and can be any of: X.509 PEM, DER or HEX formats.\n" | 
 |     "\n" | 
 |     "  -i, --pai <file/str>\n" | 
 |     "\n" | 
 |     "       File or string containing Product Attestation Intermediate (PAI) Certificate.\n" | 
 |     "       The PAI format is auto-detected and can be any of: X.509 PEM, DER or HEX formats.\n" | 
 |     "\n" | 
 |     "  -a, --paa <file/str>\n" | 
 |     "\n" | 
 |     "       File or string containing trusted Product Attestation Authority (PAA) Certificate.\n" | 
 |     "       The PAA format is auto-detected and can be any of: X.509 PEM, DER or HEX formats.\n" | 
 |     "\n" | 
 |     ; | 
 |  | 
 | OptionSet gCmdOptions = | 
 | { | 
 |     HandleOption, | 
 |     gCmdOptionDefs, | 
 |     "COMMAND OPTIONS", | 
 |     gCmdOptionHelp | 
 | }; | 
 |  | 
 | HelpOptions gHelpOptions( | 
 |     CMD_NAME, | 
 |     "Usage: " CMD_NAME " [ <options...> ]\n", | 
 |     CHIP_VERSION_STRING "\n" COPYRIGHT_STRING, | 
 |     "Validate a chain of CHIP attestation certificates" | 
 | ); | 
 |  | 
 | OptionSet * gCmdOptionSets[] = | 
 | { | 
 |     &gCmdOptions, | 
 |     &gHelpOptions, | 
 |     nullptr | 
 | }; | 
 | // clang-format on | 
 |  | 
 | const char * gDACFileNameOrStr = nullptr; | 
 | const char * gPAIFileNameOrStr = nullptr; | 
 | const char * gPAAFileNameOrStr = nullptr; | 
 |  | 
 | bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) | 
 | { | 
 |     switch (id) | 
 |     { | 
 |     case 'd': | 
 |         gDACFileNameOrStr = arg; | 
 |         break; | 
 |     case 'i': | 
 |         gPAIFileNameOrStr = arg; | 
 |         break; | 
 |     case 'a': | 
 |         gPAAFileNameOrStr = arg; | 
 |         break; | 
 |     default: | 
 |         PrintArgError("%s: Unhandled option: %s\n", progName, name); | 
 |         return false; | 
 |     } | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | AttestationVerificationResult MapError(CertificateChainValidationResult certificateChainValidationResult) | 
 | { | 
 |     switch (certificateChainValidationResult) | 
 |     { | 
 |     case CertificateChainValidationResult::kRootFormatInvalid: | 
 |         return AttestationVerificationResult::kPaaFormatInvalid; | 
 |  | 
 |     case CertificateChainValidationResult::kRootArgumentInvalid: | 
 |         return AttestationVerificationResult::kPaaArgumentInvalid; | 
 |  | 
 |     case CertificateChainValidationResult::kICAFormatInvalid: | 
 |         return AttestationVerificationResult::kPaiFormatInvalid; | 
 |  | 
 |     case CertificateChainValidationResult::kICAArgumentInvalid: | 
 |         return AttestationVerificationResult::kPaiArgumentInvalid; | 
 |  | 
 |     case CertificateChainValidationResult::kLeafFormatInvalid: | 
 |         return AttestationVerificationResult::kDacFormatInvalid; | 
 |  | 
 |     case CertificateChainValidationResult::kLeafArgumentInvalid: | 
 |         return AttestationVerificationResult::kDacArgumentInvalid; | 
 |  | 
 |     case CertificateChainValidationResult::kChainInvalid: | 
 |         return AttestationVerificationResult::kDacSignatureInvalid; | 
 |  | 
 |     case CertificateChainValidationResult::kNoMemory: | 
 |         return AttestationVerificationResult::kNoMemory; | 
 |  | 
 |     case CertificateChainValidationResult::kInternalFrameworkError: | 
 |         return AttestationVerificationResult::kInternalError; | 
 |  | 
 |     default: | 
 |         return AttestationVerificationResult::kInternalError; | 
 |     } | 
 | } | 
 |  | 
 | } // namespace | 
 |  | 
 | bool Cmd_ValidateAttCert(int argc, char * argv[]) | 
 | { | 
 |     uint8_t dacBuf[kMaxDERCertLength] = { 0 }; | 
 |     uint8_t paiBuf[kMaxDERCertLength] = { 0 }; | 
 |     uint8_t paaBuf[kMaxDERCertLength] = { 0 }; | 
 |     MutableByteSpan dac(dacBuf); | 
 |     MutableByteSpan pai(paiBuf); | 
 |     MutableByteSpan paa(paaBuf); | 
 |  | 
 |     AttestationVerificationResult attestationError = AttestationVerificationResult::kSuccess; | 
 |  | 
 |     if (argc == 1) | 
 |     { | 
 |         gHelpOptions.PrintBriefUsage(stderr); | 
 |         return true; | 
 |     } | 
 |  | 
 |     VerifyOrReturnError(ParseArgs(CMD_NAME, argc, argv, gCmdOptionSets), false); | 
 |  | 
 |     if (gDACFileNameOrStr == nullptr) | 
 |     { | 
 |         fprintf(stderr, "Please specify the DAC certificate using the --dac option.\n"); | 
 |         return false; | 
 |     } | 
 |  | 
 |     if (gPAIFileNameOrStr == nullptr) | 
 |     { | 
 |         fprintf(stderr, "Please specify the PAI certificate using the --pai option.\n"); | 
 |         return false; | 
 |     } | 
 |  | 
 |     if (gPAAFileNameOrStr == nullptr) | 
 |     { | 
 |         fprintf(stderr, "Please specify the PAA certificate using the --paa option.\n"); | 
 |         return false; | 
 |     } | 
 |  | 
 |     if (!ReadCertDER(gDACFileNameOrStr, dac)) | 
 |     { | 
 |         fprintf(stderr, "Failed to read DAC Certificate: %s\n", gDACFileNameOrStr); | 
 |         return false; | 
 |     } | 
 |     if (!ReadCertDER(gPAIFileNameOrStr, pai)) | 
 |     { | 
 |         fprintf(stderr, "Failed to read PAI Certificate: %s\n", gPAIFileNameOrStr); | 
 |         return false; | 
 |     } | 
 |     if (!ReadCertDER(gPAAFileNameOrStr, paa)) | 
 |     { | 
 |         fprintf(stderr, "Failed to read PAA Certificate: %s\n", gPAAFileNameOrStr); | 
 |         return false; | 
 |     } | 
 |  | 
 |     // Validate Proper Certificate Format | 
 |     VerifyOrExit(VerifyAttestationCertificateFormat(paa, AttestationCertType::kPAA) == CHIP_NO_ERROR, | 
 |                  attestationError = AttestationVerificationResult::kPaaFormatInvalid); | 
 |     VerifyOrExit(VerifyAttestationCertificateFormat(pai, AttestationCertType::kPAI) == CHIP_NO_ERROR, | 
 |                  attestationError = AttestationVerificationResult::kPaiFormatInvalid); | 
 |     VerifyOrExit(VerifyAttestationCertificateFormat(dac, AttestationCertType::kDAC) == CHIP_NO_ERROR, | 
 |                  attestationError = AttestationVerificationResult::kDacFormatInvalid); | 
 |  | 
 |     // Verify certificate is valid at the current time | 
 |     VerifyOrExit(IsCertificateValidAtCurrentTime(dac) == CHIP_NO_ERROR, | 
 |                  attestationError = AttestationVerificationResult::kDacExpired); | 
 |  | 
 |     // Verify that VID and PID in the certificates match. | 
 |     { | 
 |         AttestationCertVidPid dacVidPid; | 
 |         AttestationCertVidPid paiVidPid; | 
 |         AttestationCertVidPid paaVidPid; | 
 |  | 
 |         VerifyOrExit(ExtractVIDPIDFromX509Cert(dac, dacVidPid) == CHIP_NO_ERROR, | 
 |                      attestationError = AttestationVerificationResult::kDacFormatInvalid); | 
 |         VerifyOrExit(ExtractVIDPIDFromX509Cert(pai, paiVidPid) == CHIP_NO_ERROR, | 
 |                      attestationError = AttestationVerificationResult::kPaiFormatInvalid); | 
 |         VerifyOrExit(ExtractVIDPIDFromX509Cert(paa, paaVidPid) == CHIP_NO_ERROR, | 
 |                      attestationError = AttestationVerificationResult::kPaaFormatInvalid); | 
 |  | 
 |         VerifyOrExit(dacVidPid.mVendorId.HasValue() && dacVidPid.mVendorId == paiVidPid.mVendorId, | 
 |                      attestationError = AttestationVerificationResult::kDacVendorIdMismatch); | 
 |  | 
 |         if (paaVidPid.mVendorId.HasValue()) | 
 |         { | 
 |             VerifyOrExit(dacVidPid.mVendorId == paaVidPid.mVendorId, | 
 |                          attestationError = AttestationVerificationResult::kPaiVendorIdMismatch); | 
 |         } | 
 |  | 
 |         if (paiVidPid.mProductId.HasValue()) | 
 |         { | 
 |             VerifyOrExit(dacVidPid.mProductId == paiVidPid.mProductId, | 
 |                          attestationError = AttestationVerificationResult::kDacProductIdMismatch); | 
 |         } | 
 |  | 
 |         VerifyOrExit(!paaVidPid.mProductId.HasValue(), attestationError = AttestationVerificationResult::kPaaFormatInvalid); | 
 |     } | 
 |  | 
 |     // Validate certificate chain. | 
 |     chip::Crypto::CertificateChainValidationResult chainValidationResult; | 
 |     VerifyOrExit(ValidateCertificateChain(paa.data(), paa.size(), pai.data(), pai.size(), dac.data(), dac.size(), | 
 |                                           chainValidationResult) == CHIP_NO_ERROR, | 
 |                  attestationError = MapError(chainValidationResult)); | 
 |  | 
 | exit: | 
 |     if (attestationError != AttestationVerificationResult::kSuccess) | 
 |     { | 
 |         fprintf(stderr, "Attestation Certificates Validation Failed with Error Code: %d\n", static_cast<int>(attestationError)); | 
 |         return false; | 
 |     } | 
 |  | 
 |     return true; | 
 | } |