/* * * 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 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 \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 \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 \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 " [ ]\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(attestationError)); return false; } return true; }