blob: 2506f67d3607c4253b0885a769df99e32d32b881 [file] [log] [blame]
/*
*
* 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;
}