blob: 7cc8a8289196bf65b4018fba144e2152baa089ce [file] [log] [blame]
/*
*
* Copyright (c) 2021-2022 Project CHIP Authors
* Copyright (c) 2013-2017 Nest Labs, Inc.
* 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 certificate.
*
*/
#include "chip-cert.h"
#include <credentials/CHIPCertificateSet.h>
#include "vector"
namespace {
using namespace chip;
using namespace chip::ArgParser;
using namespace chip::Credentials;
using namespace chip::ASN1;
#define CMD_NAME "chip-cert validate-cert"
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[] =
{
{ "cert", kArgumentRequired, 'c' },
{ "trusted-cert", kArgumentRequired, 't' },
{ }
};
const char * const gCmdOptionHelp =
" -c, --cert <file/str>\n"
"\n"
" File or string containing an untrusted CHIP certificate to be used during\n"
" validation. Usually, it is Intermediate CA certificate (ICAC).\n"
"\n"
" -t, --trusted-cert <file/str>\n"
"\n"
" File or string containing a trusted CHIP certificate to be used during\n"
" validation. Usually, it is trust anchor root certificate (RCAC).\n"
"\n"
;
OptionSet gCmdOptions =
{
HandleOption,
gCmdOptionDefs,
"COMMAND OPTIONS",
gCmdOptionHelp
};
HelpOptions gHelpOptions(
CMD_NAME,
"Usage: " CMD_NAME " [ <options...> ] <file/str>\n",
CHIP_VERSION_STRING "\n" COPYRIGHT_STRING,
"Validate a chain of CHIP certificates.\n"
"\n"
"ARGUMENTS\n"
"\n"
" <file/str>\n"
"\n"
" File or string containing the certificate to be validated.\n"
" The formats of all input certificates are auto-detected and can be any of:\n"
" X.509 PEM, X.509 DER, X.509 HEX, CHIP base-64, CHIP raw TLV or CHIP HEX.\n"
"\n"
);
OptionSet * gCmdOptionSets[] =
{
&gCmdOptions,
&gHelpOptions,
nullptr
};
// clang-format on
enum
{
kMaxCerts = 16,
};
const char * gTargetCertFileName = nullptr;
const char * gCACertFileNames[kMaxCerts] = { nullptr };
bool gCACertIsTrusted[kMaxCerts] = { false };
size_t gNumCertFileNames = 0;
bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg)
{
switch (id)
{
case 'c':
case 't':
if (gNumCertFileNames >= kMaxCerts)
{
PrintArgError("%s: Too many certificate files\n", progName);
return false;
}
gCACertFileNames[gNumCertFileNames] = arg;
gCACertIsTrusted[gNumCertFileNames++] = (id == 't');
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 name of the certificate to be validated.\n", progName);
return false;
}
if (argc > 1)
{
PrintArgError("%s: Unexpected argument: %s\n", progName, argv[1]);
return false;
}
gTargetCertFileName = argv[0];
return true;
}
} // namespace
bool Cmd_ValidateCert(int argc, char * argv[])
{
bool res = true;
CHIP_ERROR err = CHIP_NO_ERROR;
ChipCertificateSet certSet;
const ChipCertificateData * certToBeValidated = nullptr;
const ChipCertificateData * validatedCert = nullptr;
ValidationContext context;
uint8_t certsBuf[kMaxCerts * kMaxCHIPCertLength];
MutableByteSpan chipCert[kMaxCerts];
context.Reset();
if (argc == 1)
{
gHelpOptions.PrintBriefUsage(stderr);
ExitNow(res = true);
}
res = ParseArgs(CMD_NAME, argc, argv, gCmdOptionSets, HandleNonOptionArgs);
VerifyTrueOrExit(res);
err = certSet.Init(kMaxCerts);
if (err != CHIP_NO_ERROR)
{
fprintf(stderr, "Failed to initialize certificate set: %s\n", chip::ErrorStr(err));
ExitNow(res = false);
}
for (size_t i = 0; i < gNumCertFileNames; i++)
{
chipCert[i] = MutableByteSpan(&certsBuf[i * kMaxCHIPCertLength], kMaxCHIPCertLength);
res = LoadChipCert(gCACertFileNames[i], gCACertIsTrusted[i], certSet, chipCert[i]);
VerifyTrueOrExit(res);
}
chipCert[gNumCertFileNames] = MutableByteSpan(&certsBuf[gNumCertFileNames * kMaxCHIPCertLength], kMaxCHIPCertLength);
res = LoadChipCert(gTargetCertFileName, false, certSet, chipCert[gNumCertFileNames]);
VerifyTrueOrExit(res);
certToBeValidated = certSet.GetLastCert();
context.Reset();
uint32_t currentTime;
res = chip::UnixEpochToChipEpochTime(static_cast<uint32_t>(time(nullptr)), currentTime);
context.mEffectiveTime.Set<CurrentChipEpochTime>(currentTime);
context.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature);
VerifyTrueOrExit(res);
err = certSet.FindValidCert(certToBeValidated->mSubjectDN, certToBeValidated->mSubjectKeyId, context, &validatedCert);
if (err != CHIP_NO_ERROR)
{
fprintf(stderr, "Failed certificate chain validation: %s\n", chip::ErrorStr(err));
ExitNow(res = false);
}
exit:
certSet.Release();
return res;
}