blob: 86418914405ff57ff66557afbd414ee8757cbcef [file] [log] [blame]
/*
*
* Copyright (c) 2021 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 prints the contents of a CHIP certificate.
*
*/
#include "chip-cert.h"
namespace {
using namespace chip;
using namespace chip::ArgParser;
using namespace chip::Credentials;
using namespace chip::ASN1;
#define CMD_NAME "chip-cert print-cert"
bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg);
bool HandleNonOptionArgs(const char * progName, int argc, char * argv[]);
// clang-format off
OptionDef gCmdOptionDefs[] =
{
{ "out", kArgumentRequired, 'o' },
{ }
};
const char * const gCmdOptionHelp =
" -o, --out\n"
"\n"
" The output printed certificate file name. If not specified\n"
" or if specified - then output is writen to stdout.\n"
"\n"
;
OptionSet gCmdOptions =
{
HandleOption,
gCmdOptionDefs,
"COMMAND OPTIONS",
gCmdOptionHelp
};
HelpOptions gHelpOptions(
CMD_NAME,
"Usage: " CMD_NAME " [<options...>] <cert-file>\n",
CHIP_VERSION_STRING "\n" COPYRIGHT_STRING,
"Print a CHIP certificate.\n"
"\n"
"ARGUMENTS\n"
"\n"
" <cert-file>\n"
"\n"
" A file containing a CHIP certificate.\n"
"\n"
);
OptionSet *gCmdOptionSets[] =
{
&gCmdOptions,
&gHelpOptions,
nullptr
};
// clang-format on
const char * gInFileName = nullptr;
const char * gOutFileName = nullptr;
bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg)
{
switch (id)
{
case 'o':
gOutFileName = arg;
break;
default:
PrintArgError("%s: Unhandled option: %s\n", progName, name);
return false;
}
return true;
}
bool HandleNonOptionArgs(const char * progName, int argc, char * argv[])
{
if (argc == 0)
{
PrintArgError("%s: Please specify the name of the certificate to be printed.\n", progName);
return false;
}
if (argc > 1)
{
PrintArgError("%s: Unexpected argument: %s\n", progName, argv[1]);
return false;
}
gInFileName = argv[0];
return true;
}
void Indent(FILE * file, int count)
{
while (count--)
{
fputc(' ', file);
}
}
void PrintHexField(FILE * file, const char * name, int indent, size_t count, const uint8_t * data, size_t countPerRow = 16)
{
Indent(file, indent);
indent += fprintf(file, "%s: ", name);
for (uint16_t i = 0; i < count; i++)
{
if (i != 0 && i != count && i % countPerRow == 0)
{
fprintf(file, "\n");
Indent(file, indent);
}
fprintf(file, "%02X ", data[i]);
}
fprintf(file, "\n");
}
void PrintEpochTime(FILE * file, const char * name, int indent, uint32_t epochTime)
{
chip::ASN1::ASN1UniversalTime asn1Time;
ChipEpochToASN1Time(epochTime, asn1Time);
Indent(file, indent);
fprintf(file, "%s: ", name);
fprintf(file, "0x%08" PRIX32 " ( %04" PRId16 "/%02" PRId8 "/%02" PRId8 " %02" PRId8 ":%02" PRId8 ":%02" PRId8 " )\n",
epochTime, asn1Time.Year, asn1Time.Month, asn1Time.Day, asn1Time.Hour, asn1Time.Minute, asn1Time.Second);
}
void PrintDN(FILE * file, const char * name, int indent, const ChipDN * dn)
{
uint8_t rdnCount = dn->RDNCount();
char valueStr[128];
Indent(file, indent);
indent += fprintf(file, "%s: [[ ", name);
for (uint8_t i = 0; i < rdnCount; i++)
{
if (IsChip64bitDNAttr(dn->rdn[i].mAttrOID))
{
snprintf(valueStr, sizeof(valueStr), "%016" PRIX64, dn->rdn[i].mChipVal);
}
else if (IsChip32bitDNAttr(dn->rdn[i].mAttrOID))
{
snprintf(valueStr, sizeof(valueStr), "%08" PRIX32, static_cast<uint32_t>(dn->rdn[i].mChipVal));
}
else
{
size_t len = dn->rdn[i].mString.size();
if (len > sizeof(valueStr) - 1)
{
len = sizeof(valueStr) - 1;
}
memcpy(valueStr, dn->rdn[i].mString.data(), len);
valueStr[len] = 0;
}
fprintf(file, "%s = %s", chip::ASN1::GetOIDName(dn->rdn[i].mAttrOID), valueStr);
if (i == rdnCount - 1)
{
fprintf(file, " ]]\n");
}
else
{
fprintf(file, ",\n");
Indent(file, indent);
}
}
}
bool PrintCert(const char * fileName, X509 * cert)
{
bool res = true;
CHIP_ERROR err = CHIP_NO_ERROR;
FILE * file = nullptr;
ChipCertificateSet certSet;
const ChipCertificateData * certData;
chip::BitFlags<CertDecodeFlags> decodeFlags;
uint8_t chipCertBuf[kMaxCHIPCertLength];
MutableByteSpan chipCert(chipCertBuf);
int indent = 4;
VerifyOrExit(cert != nullptr, res = false);
res = OpenFile(fileName, file, true);
VerifyTrueOrExit(res);
res = X509ToChipCert(cert, chipCert);
VerifyTrueOrExit(res);
err = certSet.Init(1);
if (err != CHIP_NO_ERROR)
{
fprintf(stderr, "Failed to initialize certificate set: %s\n", chip::ErrorStr(err));
ExitNow(res = false);
}
err = certSet.LoadCert(chipCert, decodeFlags);
if (err != CHIP_NO_ERROR)
{
fprintf(stderr, "Error reading %s: %s\n", fileName, chip::ErrorStr(err));
ExitNow(res = false);
}
certData = certSet.GetLastCert();
fprintf(file, "CHIP Certificate:\n");
Indent(file, indent);
fprintf(file, "Signature Algo : %s\n", GetOIDName(certData->mSigAlgoOID));
PrintDN(file, "Issuer ", indent, &certData->mIssuerDN);
PrintEpochTime(file, "Not Before ", indent, certData->mNotBeforeTime);
PrintEpochTime(file, "Not After ", indent, certData->mNotAfterTime);
PrintDN(file, "Subject ", indent, &certData->mSubjectDN);
Indent(file, indent);
fprintf(file, "Public Key Algo : %s\n", GetOIDName(certData->mPubKeyAlgoOID));
Indent(file, indent);
fprintf(file, "Curve Id : %s\n", GetOIDName(certData->mPubKeyCurveOID));
PrintHexField(file, "Public Key ", indent, certData->mPublicKey.size(), certData->mPublicKey.data());
Indent(file, indent);
fprintf(file, "Extensions:\n");
indent += 4;
if (certData->mCertFlags.Has(CertFlags::kIsCA))
{
Indent(file, indent);
fprintf(file, "Is CA : true\n");
}
if (certData->mCertFlags.Has(CertFlags::kPathLenConstraintPresent))
{
Indent(file, indent);
fprintf(file, "Path Length Const: %u\n", (unsigned) certData->mPathLenConstraint);
}
if (certData->mCertFlags.Has(CertFlags::kExtPresent_KeyUsage))
{
Indent(file, indent);
fprintf(file, "Key Usage : ");
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kDigitalSignature))
{
fprintf(file, "DigitalSignature ");
}
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kNonRepudiation))
{
fprintf(file, "NonRepudiation ");
}
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kKeyEncipherment))
{
fprintf(file, "KeyEncipherment ");
}
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kDataEncipherment))
{
fprintf(file, "DataEncipherment ");
}
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kKeyAgreement))
{
fprintf(file, "KeyAgreement ");
}
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kKeyCertSign))
{
fprintf(file, "KeyCertSign ");
}
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kCRLSign))
{
fprintf(file, "CRLSign ");
}
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kEncipherOnly))
{
fprintf(file, "EncipherOnly ");
}
if (certData->mKeyUsageFlags.Has(KeyUsageFlags::kDecipherOnly))
{
fprintf(file, "DecipherOnly ");
}
fprintf(file, "\n");
}
if (certData->mCertFlags.Has(CertFlags::kExtPresent_ExtendedKeyUsage))
{
Indent(file, indent);
fprintf(file, "Key Purpose : ");
if (certData->mKeyPurposeFlags.Has(KeyPurposeFlags::kServerAuth))
{
fprintf(file, "ServerAuth ");
}
if (certData->mKeyPurposeFlags.Has(KeyPurposeFlags::kClientAuth))
{
fprintf(file, "ClientAuth ");
}
if (certData->mKeyPurposeFlags.Has(KeyPurposeFlags::kCodeSigning))
{
fprintf(file, "CodeSigning ");
}
if (certData->mKeyPurposeFlags.Has(KeyPurposeFlags::kEmailProtection))
{
fprintf(file, "EmailProtection ");
}
if (certData->mKeyPurposeFlags.Has(KeyPurposeFlags::kTimeStamping))
{
fprintf(file, "TimeStamping ");
}
if (certData->mKeyPurposeFlags.Has(KeyPurposeFlags::kOCSPSigning))
{
fprintf(file, "OCSPSigning ");
}
fprintf(file, "\n");
}
if (certData->mCertFlags.Has(CertFlags::kExtPresent_SubjectKeyId))
{
PrintHexField(file, "Subject Key Id ", indent, certData->mSubjectKeyId.size(), certData->mSubjectKeyId.data(),
certData->mSubjectKeyId.size());
}
if (certData->mCertFlags.Has(CertFlags::kExtPresent_AuthKeyId))
{
PrintHexField(file, "Authority Key Id ", indent, certData->mAuthKeyId.size(), certData->mAuthKeyId.data(),
certData->mAuthKeyId.size());
}
indent -= 4;
PrintHexField(file, "Signature ", indent, certData->mSignature.size(), certData->mSignature.data());
exit:
CloseFile(file);
return res;
}
} // namespace
bool Cmd_PrintCert(int argc, char * argv[])
{
bool res = true;
std::unique_ptr<X509, void (*)(X509 *)> cert(X509_new(), &X509_free);
if (argc == 1)
{
gHelpOptions.PrintBriefUsage(stderr);
ExitNow(res = true);
}
res = ParseArgs(CMD_NAME, argc, argv, gCmdOptionSets, HandleNonOptionArgs);
VerifyTrueOrExit(res);
res = ReadCert(gInFileName, cert.get());
VerifyTrueOrExit(res);
res = PrintCert(gOutFileName, cert.get());
VerifyTrueOrExit(res);
exit:
return res;
}