From 33adafaf1d9f7d69482b7b4c6f7e8a9dfc07829f Mon Sep 17 00:00:00 2001 From: Evgeni Margolis Date: Wed, 15 Jun 2022 05:40:28 -0700 Subject: [PATCH] [chip-cert] Updated Tool To Generate Invalid Operational Certificates. - Updated current code to inject errors into operational certs in DER/PEM format. - New code to generate CHIP TLV encoded certificates with injected errors. --- src/tools/chip-cert/CertUtils.cpp | 680 +++++++++++++++++++------ src/tools/chip-cert/Cmd_GenAttCert.cpp | 6 +- src/tools/chip-cert/Cmd_GenCD.cpp | 1 - src/tools/chip-cert/Cmd_GenCert.cpp | 372 +++++++++++++- src/tools/chip-cert/chip-cert.h | 181 +++++-- 5 files changed, 1027 insertions(+), 213 deletions(-) diff --git a/src/tools/chip-cert/CertUtils.cpp b/src/tools/chip-cert/CertUtils.cpp index 60a44544385070..8c0991a7c1a0ca 100644 --- a/src/tools/chip-cert/CertUtils.cpp +++ b/src/tools/chip-cert/CertUtils.cpp @@ -27,6 +27,7 @@ #define __STDC_FORMAT_MACROS #include "chip-cert.h" +#include #include #include @@ -34,8 +35,9 @@ using namespace chip; using namespace chip::Credentials; using namespace chip::ASN1; +using namespace chip::TLV; -bool ToolChipDN::SetCertSubjectDN(X509 * cert) const +bool ToolChipDN::SetCertName(X509_NAME * name) const { bool res = true; uint8_t rdnCount = RDNCount(); @@ -77,8 +79,8 @@ bool ToolChipDN::SetCertSubjectDN(X509 * cert) const Encoding::HexFlags::kUppercase) == CHIP_NO_ERROR, false); - if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), attrNID, MBSTRING_UTF8, - reinterpret_cast(chipAttrStr), sizeof(chipAttrStr), -1, 0)) + if (!X509_NAME_add_entry_by_NID(name, attrNID, MBSTRING_UTF8, reinterpret_cast(chipAttrStr), + sizeof(chipAttrStr), -1, 0)) { ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); } @@ -90,15 +92,15 @@ bool ToolChipDN::SetCertSubjectDN(X509 * cert) const Encoding::HexFlags::kUppercase) == CHIP_NO_ERROR, false); - if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), attrNID, MBSTRING_UTF8, - reinterpret_cast(chipAttrStr), sizeof(chipAttrStr), -1, 0)) + if (!X509_NAME_add_entry_by_NID(name, attrNID, MBSTRING_UTF8, reinterpret_cast(chipAttrStr), + sizeof(chipAttrStr), -1, 0)) { ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); } } else { - if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), attrNID, MBSTRING_UTF8, + if (!X509_NAME_add_entry_by_NID(name, attrNID, MBSTRING_UTF8, reinterpret_cast(const_cast(rdn[i].mString.data())), static_cast(rdn[i].mString.size()), -1, 0)) { @@ -247,9 +249,10 @@ bool SetCertTimeField(ASN1_TIME * asn1Time, const struct tm & value) return true; } -bool SetValidityTime(X509 * cert, const struct tm & validFrom, uint32_t validDays) +bool SetValidityTime(X509 * cert, const struct tm & validFrom, uint32_t validDays, CertStructConfig & certConfig) { bool res = true; + struct tm validFromLocal; struct tm validTo; time_t validToTime; @@ -283,13 +286,30 @@ bool SetValidityTime(X509 * cert, const struct tm & validFrom, uint32_t validDay localtime_r(&validToTime, &validTo); } + if (certConfig.IsValidityCorrect()) + { + validFromLocal = validFrom; + } + else + { + // Switch values if error flag is set. + validFromLocal = validTo; + validTo = validFrom; + } + // Set the certificate's notBefore date. - res = SetCertTimeField(X509_get_notBefore(cert), validFrom); - VerifyTrueOrExit(res); + if (certConfig.IsValidityNotBeforePresent()) + { + res = SetCertTimeField(X509_get_notBefore(cert), validFromLocal); + VerifyTrueOrExit(res); + } // Set the certificate's notAfter date. - res = SetCertTimeField(X509_get_notAfter(cert), validTo); - VerifyTrueOrExit(res); + if (certConfig.IsValidityNotAfterPresent()) + { + res = SetCertTimeField(X509_get_notAfter(cert), validTo); + VerifyTrueOrExit(res); + } exit: return true; @@ -310,6 +330,104 @@ bool AddExtension(X509 * cert, int extNID, const char * extStr) return res; } +bool SetBasicConstraintsExtension(X509 * cert, bool isCA, int pathLen, CertStructConfig & certConfig) +{ + if (!certConfig.IsExtensionBasicPresent()) + { + return true; + } + + std::string basicConstraintsExt; + + if (certConfig.IsExtensionBasicCriticalPresent() && certConfig.IsExtensionBasicCritical()) + { + basicConstraintsExt += "critical"; + } + + if (certConfig.IsExtensionBasicCAPresent()) + { + if (!basicConstraintsExt.empty()) + { + basicConstraintsExt += ","; + } + if ((certConfig.IsExtensionBasicCACorrect() && !isCA) || (!certConfig.IsExtensionBasicCACorrect() && isCA)) + { + basicConstraintsExt += "CA:FALSE"; + } + else + { + basicConstraintsExt += "CA:TRUE"; + } + } + + if (pathLen != kPathLength_NotSpecified) + { + if (!basicConstraintsExt.empty()) + { + basicConstraintsExt += ","; + } + basicConstraintsExt.append("pathlen:" + std::to_string(pathLen)); + } + + return AddExtension(cert, NID_basic_constraints, basicConstraintsExt.c_str()); +} + +bool SetKeyUsageExtension(X509 * cert, bool isCA, CertStructConfig & certConfig) +{ + if (!certConfig.IsExtensionKeyUsagePresent()) + { + return true; + } + + std::string keyUsageExt; + + if (certConfig.IsExtensionKeyUsageCriticalPresent() && certConfig.IsExtensionKeyUsageCritical()) + { + keyUsageExt += "critical"; + } + + if ((certConfig.IsExtensionKeyUsageDigitalSigCorrect() && !isCA) || + (!certConfig.IsExtensionKeyUsageDigitalSigCorrect() && isCA)) + { + if (!keyUsageExt.empty()) + { + keyUsageExt += ","; + } + keyUsageExt += "digitalSignature"; + } + + if ((certConfig.IsExtensionKeyUsageKeyCertSignCorrect() && isCA) || + (!certConfig.IsExtensionKeyUsageKeyCertSignCorrect() && !isCA)) + { + if (!keyUsageExt.empty()) + { + keyUsageExt += ","; + } + keyUsageExt += "keyCertSign"; + } + + if ((certConfig.IsExtensionKeyUsageCRLSignCorrect() && isCA) || (!certConfig.IsExtensionKeyUsageCRLSignCorrect() && !isCA)) + { + if (!keyUsageExt.empty()) + { + keyUsageExt += ","; + } + keyUsageExt += "cRLSign"; + } + + // In test mode only: just add an extra extension flag to prevent empty extantion. + if (certConfig.IsErrorTestCaseEnabled() && (keyUsageExt.empty() || (keyUsageExt.compare("critical") == 0))) + { + if (!keyUsageExt.empty()) + { + keyUsageExt += ","; + } + keyUsageExt += "keyEncipherment"; + } + + return AddExtension(cert, NID_key_usage, keyUsageExt.c_str()); +} + /** The key identifier field is derived from the public key using method (1) per RFC5280 (section 4.2.1.2): * * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the @@ -605,11 +723,52 @@ bool WriteCert(const char * fileName, X509 * cert, CertFormat certFmt) return res; } +bool WriteChipCert(const char * fileName, const ByteSpan & chipCert, CertFormat certFmt) +{ + bool res = true; + FILE * file = nullptr; + const uint8_t * certToWrite = nullptr; + size_t certToWriteLen = 0; + uint32_t chipCertBase64Len = BASE64_ENCODED_LEN(static_cast(chipCert.size())); + std::unique_ptr chipCertBase64(new uint8_t[chipCertBase64Len]); + + VerifyOrReturnError(certFmt == kCertFormat_Chip_Raw || certFmt == kCertFormat_Chip_Base64, false); + + if (certFmt == kCertFormat_Chip_Base64) + { + res = Base64Encode(chipCert.data(), static_cast(chipCert.size()), chipCertBase64.get(), chipCertBase64Len, + chipCertBase64Len); + VerifyTrueOrExit(res); + + certToWrite = chipCertBase64.get(); + certToWriteLen = chipCertBase64Len; + } + else + { + certToWrite = chipCert.data(); + certToWriteLen = chipCert.size(); + } + + res = OpenFile(fileName, file, true); + VerifyTrueOrExit(res); + + if (fwrite(certToWrite, 1, certToWriteLen, file) != certToWriteLen) + { + fprintf(stderr, "Unable to write to %s: %s\n", fileName, strerror(ferror(file) ? errno : ENOSPC)); + ExitNow(res = false); + } + +exit: + CloseFile(file); + return res; +} + bool MakeCert(uint8_t certType, const ToolChipDN * subjectDN, X509 * caCert, EVP_PKEY * caKey, const struct tm & validFrom, uint32_t validDays, int pathLen, const FutureExtension * futureExts, uint8_t futureExtsCount, X509 * newCert, - EVP_PKEY * newKey) + EVP_PKEY * newKey, CertStructConfig & certConfig) { - bool res = true; + bool res = true; + bool isCA = (certType != kCertType_Node); VerifyOrExit(subjectDN != nullptr, res = false); VerifyOrExit(caCert != nullptr, res = false); @@ -618,17 +777,38 @@ bool MakeCert(uint8_t certType, const ToolChipDN * subjectDN, X509 * caCert, EVP VerifyOrExit(newKey != nullptr, res = false); // Set the certificate version (must be 2, a.k.a. v3). - if (!X509_set_version(newCert, 2)) + if (!X509_set_version(newCert, certConfig.GetCertVersion())) { ReportOpenSSLErrorAndExit("X509_set_version", res = false); } // Generate a serial number for the cert. - res = SetCertSerialNumber(newCert); - VerifyTrueOrExit(res); + if (certConfig.IsSerialNumberPresent()) + { + res = SetCertSerialNumber(newCert); + VerifyTrueOrExit(res); + } + + // Set the issuer name for the certificate. In the case of a self-signed cert, this will be + // the new cert's subject name. + if (certConfig.IsIssuerPresent()) + { + if (certType == kCertType_Root) + { + res = subjectDN->SetCertIssuerDN(newCert); + VerifyTrueOrExit(res); + } + else + { + if (!X509_set_issuer_name(newCert, X509_get_subject_name(caCert))) + { + ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false); + } + } + } // Set the certificate validity time. - res = SetValidityTime(newCert, validFrom, validDays); + res = SetValidityTime(newCert, validFrom, validDays, certConfig); VerifyTrueOrExit(res); // Set the certificate's public key. @@ -637,79 +817,355 @@ bool MakeCert(uint8_t certType, const ToolChipDN * subjectDN, X509 * caCert, EVP ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false); } + // Injuct error into public key value. + if (certConfig.IsPublicKeyError()) + { + ASN1_BIT_STRING * pk = X509_get0_pubkey_bitstr(newCert); + pk->data[CertStructConfig::kPublicKeyErrorByte] ^= 0xFF; + } + // Set certificate subject DN. - res = subjectDN->SetCertSubjectDN(newCert); + if (certConfig.IsSubjectPresent()) + { + res = subjectDN->SetCertSubjectDN(newCert); + VerifyTrueOrExit(res); + } + + // Add basic constraints certificate extensions. + if (certConfig.IsExtensionBasicPathLenPresent() || !certConfig.IsExtensionBasicCAPresent()) + { + pathLen = certConfig.GetExtensionBasicPathLenValue(certType); + } + res = SetBasicConstraintsExtension(newCert, isCA, pathLen, certConfig); VerifyTrueOrExit(res); - // Set the issuer name for the certificate. In the case of a self-signed cert, this will be - // the new cert's subject name. - if (!X509_set_issuer_name(newCert, X509_get_subject_name(caCert))) + // Add key usage certificate extensions. + res = SetKeyUsageExtension(newCert, isCA, certConfig); + VerifyTrueOrExit(res); + + // Add extended key usage certificate extensions. + if (!certConfig.IsExtensionExtendedKeyUsageMissing()) { - ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false); + if (certType == kCertType_Node) + { + res = AddExtension(newCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"); + VerifyTrueOrExit(res); + } + else if (certType == kCertType_FirmwareSigning) + { + res = AddExtension(newCert, NID_ext_key_usage, "critical,codeSigning"); + VerifyTrueOrExit(res); + } } - // Add basic constraints certificate extensions. + // Add a subject key id extension for the certificate. + if (certConfig.IsExtensionSKIDPresent()) { - std::string basicConstraintsExt; + res = AddSubjectKeyId(newCert); + VerifyTrueOrExit(res); + } - if (certType == kCertType_Node || certType == kCertType_FirmwareSigning) + // Add the authority key id extension from the signing certificate. For self-signed cert's this will + // be the same as new cert's subject key id extension. + if (certConfig.IsExtensionAKIDPresent()) + { + if ((certType == kCertType_Root) && !certConfig.IsExtensionSKIDPresent()) { - basicConstraintsExt = "critical,CA:FALSE"; + res = AddSubjectKeyId(newCert); + VerifyTrueOrExit(res); + res = AddAuthorityKeyId(newCert, newCert); + VerifyTrueOrExit(res); + + // Remove that temporary added subject key id + int authKeyIdExtLoc = X509_get_ext_by_NID(newCert, NID_subject_key_identifier, -1); + if (authKeyIdExtLoc != -1) + { + if (X509_delete_ext(newCert, authKeyIdExtLoc) == nullptr) + { + ReportOpenSSLErrorAndExit("X509_delete_ext", res = false); + } + } } else { - basicConstraintsExt = "critical,CA:TRUE"; + res = AddAuthorityKeyId(newCert, caCert); + VerifyTrueOrExit(res); } + } + for (uint8_t i = 0; i < futureExtsCount; i++) + { + res = AddExtension(newCert, futureExts[i].nid, futureExts[i].info); + VerifyTrueOrExit(res); + } - if (pathLen != kPathLength_NotSpecified) + // Sign the new certificate. + if (!X509_sign(newCert, caKey, certConfig.GetSignatureAlgorithmDER())) + { + ReportOpenSSLErrorAndExit("X509_sign", res = false); + } + + // Injuct error into signature value. + if (certConfig.IsSignatureError()) + { + const ASN1_BIT_STRING * sig = nullptr; + X509_get0_signature(&sig, nullptr, newCert); + sig->data[20] ^= 0xFF; + } + +exit: + return res; +} + +CHIP_ERROR MakeCertChipTLV(uint8_t certType, const ToolChipDN * subjectDN, X509 * caCert, EVP_PKEY * caKey, + const struct tm & validFrom, uint32_t validDays, int pathLen, const FutureExtension * futureExts, + uint8_t futureExtsCount, X509 * x509Cert, EVP_PKEY * newKey, CertStructConfig & certConfig, + MutableByteSpan & chipCert) +{ + TLVWriter writer; + TLVType containerType; + TLVType containerType2; + TLVType containerType3; + uint8_t subjectPubkey[chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES] = { 0 }; + uint8_t issuerPubkey[chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES] = { 0 }; + uint8_t keyid[chip::Crypto::kSHA1_Hash_Length] = { 0 }; + bool isCA; + + VerifyOrReturnError(subjectDN != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(caCert != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(caKey != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(x509Cert != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(newKey != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + isCA = (certType == kCertType_ICA || certType == kCertType_Root); + + uint8_t * p = subjectPubkey; + VerifyOrReturnError(i2o_ECPublicKey(EVP_PKEY_get0_EC_KEY(newKey), &p) == chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES, + CHIP_ERROR_INVALID_ARGUMENT); + + p = issuerPubkey; + VerifyOrReturnError(i2o_ECPublicKey(EVP_PKEY_get0_EC_KEY(caKey), &p) == chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES, + CHIP_ERROR_INVALID_ARGUMENT); + + writer.Init(chipCert); + + ReturnErrorOnFailure(writer.StartContainer(AnonymousTag(), kTLVType_Structure, containerType)); + + // serial number + if (certConfig.IsSerialNumberPresent()) + { + ASN1_INTEGER * asn1Integer = X509_get_serialNumber(x509Cert); + uint64_t serialNumber; + uint8_t serialNumberArray[sizeof(uint64_t)]; + VerifyOrReturnError(1 == ASN1_INTEGER_get_uint64(&serialNumber, asn1Integer), CHIP_ERROR_INVALID_ARGUMENT); + Encoding::BigEndian::Put64(serialNumberArray, serialNumber); + ReturnErrorOnFailure(writer.PutBytes(ContextTag(kTag_SerialNumber), serialNumberArray, sizeof(serialNumberArray))); + } + + // signature algorithm + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_SignatureAlgorithm), certConfig.GetSignatureAlgorithmTLVEnum())); + + // issuer Name + if (certConfig.IsIssuerPresent()) + { + if (certType == kCertType_Root) { - basicConstraintsExt.append(",pathlen:" + std::to_string(pathLen)); + ReturnErrorOnFailure(subjectDN->EncodeToTLV(writer, ContextTag(kTag_Issuer))); + } + else + { + uint8_t caChipCertBuf[kMaxCHIPCertLength]; + MutableByteSpan caChipCert(caChipCertBuf); + VerifyOrReturnError(true == X509ToChipCert(caCert, caChipCert), CHIP_ERROR_INVALID_ARGUMENT); + ChipDN issuerDN; + ReturnErrorOnFailure(ExtractSubjectDNFromChipCert(caChipCert, issuerDN)); + ReturnErrorOnFailure(issuerDN.EncodeToTLV(writer, ContextTag(kTag_Issuer))); } - - res = AddExtension(newCert, NID_basic_constraints, basicConstraintsExt.c_str()); - VerifyTrueOrExit(res); } - // Add the appropriate certificate extensions. - if (certType == kCertType_Node) + // validity + uint32_t validFromChipEpoch; + uint32_t validToChipEpoch; + + VerifyOrReturnError(true == + CalendarToChipEpochTime( + static_cast(validFrom.tm_year + 1900), static_cast(validFrom.tm_mon + 1), + static_cast(validFrom.tm_mday), static_cast(validFrom.tm_hour), + static_cast(validFrom.tm_min), static_cast(validFrom.tm_sec), validFromChipEpoch), + CHIP_ERROR_INVALID_ARGUMENT); + if (validDays == kCertValidDays_NoWellDefinedExpiration) { - res = AddExtension(newCert, NID_key_usage, "critical,digitalSignature") && - AddExtension(newCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"); + validToChipEpoch = 0; } - else if (certType == kCertType_FirmwareSigning) + else { - res = AddExtension(newCert, NID_key_usage, "critical,digitalSignature") && - AddExtension(newCert, NID_ext_key_usage, "critical,codeSigning"); + VerifyOrReturnError(CanCastTo(validFromChipEpoch + validDays * kSecondsPerDay - 1), CHIP_ERROR_INVALID_ARGUMENT); + validToChipEpoch = validFromChipEpoch + validDays * kSecondsPerDay - 1; } - else if (certType == kCertType_ICA || certType == kCertType_Root) + if (!certConfig.IsValidityCorrect()) { - res = AddExtension(newCert, NID_key_usage, "critical,keyCertSign,cRLSign"); + uint32_t validTemp = validFromChipEpoch; + validFromChipEpoch = validToChipEpoch; + validToChipEpoch = validTemp; + } + if (certConfig.IsValidityNotBeforePresent()) + { + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_NotBefore), validFromChipEpoch)); + } + if (certConfig.IsValidityNotAfterPresent()) + { + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_NotAfter), validToChipEpoch)); } - VerifyTrueOrExit(res); - // Add a subject key id extension for the certificate. - res = AddSubjectKeyId(newCert); - VerifyTrueOrExit(res); + // subject Name + if (certConfig.IsSubjectPresent()) + { + ReturnErrorOnFailure(subjectDN->EncodeToTLV(writer, ContextTag(kTag_Subject))); + } - // Add the authority key id extension from the signing certificate. For self-signed cert's this will - // be the same as new cert's subject key id extension. - res = AddAuthorityKeyId(newCert, caCert); - VerifyTrueOrExit(res); + // public key algorithm + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_PublicKeyAlgorithm), GetOIDEnum(kOID_PubKeyAlgo_ECPublicKey))); - for (uint8_t i = 0; i < futureExtsCount; i++) + // public key curve Id + uint8_t ecCurveEnum = certConfig.IsSigCurveWrong() ? 0x02 : GetOIDEnum(kOID_EllipticCurve_prime256v1); + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_EllipticCurveIdentifier), ecCurveEnum)); + + // public key + if (certConfig.IsPublicKeyError()) { - res = AddExtension(newCert, futureExts[i].nid, futureExts[i].info); - VerifyTrueOrExit(res); + subjectPubkey[CertStructConfig::kPublicKeyErrorByte] ^= 0xFF; } + ReturnErrorOnFailure( + writer.PutBytes(ContextTag(kTag_EllipticCurvePublicKey), subjectPubkey, chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES)); - // Sign the new certificate. - if (!X509_sign(newCert, caKey, EVP_sha256())) + // extensions + ReturnErrorOnFailure(writer.StartContainer(ContextTag(kTag_Extensions), kTLVType_List, containerType2)); { - ReportOpenSSLErrorAndExit("X509_sign", res = false); + if (isCA) + { + // basic constraints + if (certConfig.IsExtensionBasicPresent()) + { + ReturnErrorOnFailure(writer.StartContainer(ContextTag(kTag_BasicConstraints), kTLVType_Structure, containerType3)); + if (certConfig.IsExtensionBasicCAPresent()) + { + ReturnErrorOnFailure(writer.PutBoolean(ContextTag(kTag_BasicConstraints_IsCA), + certConfig.IsExtensionBasicCACorrect() ? true : false)); + } + // TODO + if (pathLen != kPathLength_NotSpecified) + { + ReturnErrorOnFailure( + writer.Put(ContextTag(kTag_BasicConstraints_PathLenConstraint), static_cast(pathLen))); + } + ReturnErrorOnFailure(writer.EndContainer(containerType3)); + } + + // key usage + if (certConfig.IsExtensionKeyUsagePresent()) + { + BitFlags keyUsage; + if (!certConfig.IsExtensionKeyUsageDigitalSigCorrect()) + { + keyUsage.Set(KeyUsageFlags::kDigitalSignature); + } + if (certConfig.IsExtensionKeyUsageKeyCertSignCorrect()) + { + keyUsage.Set(KeyUsageFlags::kKeyCertSign); + } + if (certConfig.IsExtensionKeyUsageCRLSignCorrect()) + { + keyUsage.Set(KeyUsageFlags::kCRLSign); + } + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_KeyUsage), keyUsage.Raw())); + } + } + else + { + // basic constraints + if (certConfig.IsExtensionBasicPresent()) + { + ReturnErrorOnFailure(writer.StartContainer(ContextTag(kTag_BasicConstraints), kTLVType_Structure, containerType3)); + ReturnErrorOnFailure(writer.PutBoolean(ContextTag(kTag_BasicConstraints_IsCA), false)); + ReturnErrorOnFailure(writer.EndContainer(containerType3)); + } + + // key usage + if (certConfig.IsExtensionKeyUsagePresent()) + { + BitFlags keyUsage; + if (certConfig.IsExtensionKeyUsageDigitalSigCorrect()) + { + keyUsage.Set(KeyUsageFlags::kDigitalSignature); + } + if (!certConfig.IsExtensionKeyUsageKeyCertSignCorrect()) + { + keyUsage.Set(KeyUsageFlags::kKeyCertSign); + } + if (!certConfig.IsExtensionKeyUsageCRLSignCorrect()) + { + keyUsage.Set(KeyUsageFlags::kCRLSign); + } + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_KeyUsage), keyUsage)); + } + + // extended key usage + if (!certConfig.IsExtensionExtendedKeyUsageMissing() && (certType == kCertType_Node)) + { + ReturnErrorOnFailure(writer.StartContainer(ContextTag(kTag_ExtendedKeyUsage), kTLVType_Array, containerType3)); + if (certType == kCertType_Node) + { + ReturnErrorOnFailure(writer.Put(AnonymousTag(), GetOIDEnum(kOID_KeyPurpose_ClientAuth))); + ReturnErrorOnFailure(writer.Put(AnonymousTag(), GetOIDEnum(kOID_KeyPurpose_ServerAuth))); + } + else if (certType == kCertType_FirmwareSigning) + { + ReturnErrorOnFailure(writer.Put(AnonymousTag(), GetOIDEnum(kOID_KeyPurpose_CodeSigning))); + } + ReturnErrorOnFailure(writer.EndContainer(containerType3)); + } + } + + // subject key identifier + if (certConfig.IsExtensionSKIDPresent()) + { + ReturnErrorOnFailure(Crypto::Hash_SHA1(subjectPubkey, sizeof(subjectPubkey), keyid)); + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_SubjectKeyIdentifier), ByteSpan(keyid))); + } + + // authority key identifier + if (certConfig.IsExtensionAKIDPresent()) + { + ReturnErrorOnFailure(Crypto::Hash_SHA1(issuerPubkey, sizeof(issuerPubkey), keyid)); + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_AuthorityKeyIdentifier), ByteSpan(keyid))); + } + + for (uint8_t i = 0; i < futureExtsCount; i++) + { + ReturnErrorOnFailure( + writer.Put(ContextTag(kTag_FutureExtension), + ByteSpan(reinterpret_cast(futureExts[i].info), strlen(futureExts[i].info)))); + } } + ReturnErrorOnFailure(writer.EndContainer(containerType2)); -exit: - return res; + // signature + const ASN1_BIT_STRING * asn1Signature = nullptr; + X509_get0_signature(&asn1Signature, nullptr, x509Cert); + + uint8_t signatureRawBuf[chip::Crypto::kP256_ECDSA_Signature_Length_Raw]; + MutableByteSpan signatureRaw(signatureRawBuf); + ReturnErrorOnFailure(chip::Crypto::EcdsaAsn1SignatureToRaw( + chip::Crypto::kP256_FE_Length, ByteSpan(asn1Signature->data, static_cast(asn1Signature->length)), signatureRaw)); + + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_ECDSASignature), signatureRaw)); + + ReturnErrorOnFailure(writer.EndContainer(containerType)); + + ReturnErrorOnFailure(writer.Finalize()); + + chipCert.reduce_size(writer.GetLengthWritten()); + + return CHIP_NO_ERROR; } bool ResignCert(X509 * cert, X509 * caCert, EVP_PKEY * caKey) @@ -749,11 +1205,13 @@ bool ResignCert(X509 * cert, X509 * caCert, EVP_PKEY * caKey) bool MakeAttCert(AttCertType attCertType, const char * subjectCN, uint16_t subjectVID, uint16_t subjectPID, bool encodeVIDandPIDasCN, X509 * caCert, EVP_PKEY * caKey, const struct tm & validFrom, uint32_t validDays, - X509 * newCert, EVP_PKEY * newKey, AttCertStructConfig & certConfig) + X509 * newCert, EVP_PKEY * newKey, CertStructConfig & certConfig) { bool res = true; uint16_t vid = certConfig.IsSubjectVIDMismatch() ? static_cast(subjectVID + 1) : subjectVID; uint16_t pid = certConfig.IsSubjectPIDMismatch() ? static_cast(subjectPID + 1) : subjectPID; + bool isCA = (attCertType != kAttCertType_DAC); + int pathLen = kPathLength_NotSpecified; VerifyOrReturnError(subjectCN != nullptr, false); VerifyOrReturnError(caCert != nullptr, false); @@ -771,7 +1229,7 @@ bool MakeAttCert(AttCertType attCertType, const char * subjectCN, uint16_t subje VerifyTrueOrExit(res); // Set the certificate validity time. - res = SetValidityTime(newCert, validFrom, validDays); + res = SetValidityTime(newCert, validFrom, validDays, certConfig); VerifyTrueOrExit(res); // Set the certificate's public key. @@ -889,103 +1347,17 @@ bool MakeAttCert(AttCertType attCertType, const char * subjectCN, uint16_t subje ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false); } - if (certConfig.IsExtensionBasicPresent()) + // Add basic constraints certificate extensions. + if (certConfig.IsExtensionBasicPathLenPresent(attCertType) || !certConfig.IsExtensionBasicCAPresent()) { - std::string basicConstraintsExt; - - if (certConfig.IsExtensionBasicCriticalPresent()) - { - if (certConfig.IsExtensionBasicCritical()) - { - basicConstraintsExt += "critical"; - } - } - - if (certConfig.IsExtensionBasicCAPresent()) - { - if (!basicConstraintsExt.empty()) - { - basicConstraintsExt += ","; - } - if ((certConfig.IsExtensionBasicCACorrect() && attCertType == kAttCertType_DAC) || - (!certConfig.IsExtensionBasicCACorrect() && attCertType != kAttCertType_DAC)) - { - basicConstraintsExt += "CA:FALSE"; - } - else - { - basicConstraintsExt += "CA:TRUE"; - } - } - - if (certConfig.IsExtensionBasicPathLenPresent(attCertType) || !certConfig.IsExtensionBasicCAPresent()) - { - if (!basicConstraintsExt.empty()) - { - basicConstraintsExt += ","; - } - basicConstraintsExt.append("pathlen:" + std::to_string(certConfig.GetExtensionBasicPathLenValue(attCertType))); - } - - res = AddExtension(newCert, NID_basic_constraints, basicConstraintsExt.c_str()); - VerifyTrueOrExit(res); + pathLen = certConfig.GetExtensionBasicPathLenValue(attCertType); } + res = SetBasicConstraintsExtension(newCert, isCA, pathLen, certConfig); + VerifyTrueOrExit(res); - if (certConfig.IsExtensionKeyUsagePresent()) - { - std::string keyUsageExt; - - if (certConfig.IsExtensionKeyUsageCriticalPresent()) - { - if (certConfig.IsExtensionKeyUsageCritical()) - { - keyUsageExt += "critical"; - } - } - - if ((certConfig.IsExtensionKeyUsageDigitalSigCorrect() && attCertType == kAttCertType_DAC) || - (!certConfig.IsExtensionKeyUsageDigitalSigCorrect() && attCertType != kAttCertType_DAC)) - { - if (!keyUsageExt.empty()) - { - keyUsageExt += ","; - } - keyUsageExt += "digitalSignature"; - } - - if ((certConfig.IsExtensionKeyUsageKeyCertSignCorrect() && attCertType != kAttCertType_DAC) || - (!certConfig.IsExtensionKeyUsageKeyCertSignCorrect() && attCertType == kAttCertType_DAC)) - { - if (!keyUsageExt.empty()) - { - keyUsageExt += ","; - } - keyUsageExt += "keyCertSign"; - } - - if ((certConfig.IsExtensionKeyUsageCRLSignCorrect() && attCertType != kAttCertType_DAC) || - (!certConfig.IsExtensionKeyUsageCRLSignCorrect() && attCertType == kAttCertType_DAC)) - { - if (!keyUsageExt.empty()) - { - keyUsageExt += ","; - } - keyUsageExt += "cRLSign"; - } - - // In test mode only: just add an extra extension flag to prevent empty extantion. - if (certConfig.IsErrorTestCaseEnabled() && (keyUsageExt.empty() || (keyUsageExt.compare("critical") == 0))) - { - if (!keyUsageExt.empty()) - { - keyUsageExt += ","; - } - keyUsageExt += "keyEncipherment"; - } - - res = AddExtension(newCert, NID_key_usage, keyUsageExt.c_str()); - VerifyTrueOrExit(res); - } + // Add key usage certificate extensions. + res = SetKeyUsageExtension(newCert, isCA, certConfig); + VerifyTrueOrExit(res); if (certConfig.IsExtensionSKIDPresent()) { @@ -1023,7 +1395,7 @@ bool MakeAttCert(AttCertType attCertType, const char * subjectCN, uint16_t subje } // Sign the new certificate. - if (!X509_sign(newCert, caKey, certConfig.GetSignatureAlgorithm())) + if (!X509_sign(newCert, caKey, certConfig.GetSignatureAlgorithmDER())) { ReportOpenSSLErrorAndExit("X509_sign", res = false); } diff --git a/src/tools/chip-cert/Cmd_GenAttCert.cpp b/src/tools/chip-cert/Cmd_GenAttCert.cpp index c91ce8bee6e735..a67fbe1219b16e 100644 --- a/src/tools/chip-cert/Cmd_GenAttCert.cpp +++ b/src/tools/chip-cert/Cmd_GenAttCert.cpp @@ -173,7 +173,7 @@ const char * const gCmdOptionHelp = " ext-authority-info-access - Certificate will include optional Authority Information Access extension.\n" " ext-subject-alt-name - Certificate will include optional Subject Alternative Name extension.\n" "\n" -#endif +#endif // CHIP_CONFIG_INTERNAL_FLAG_GENERATE_DA_TEST_CASES ; OptionSet gCmdOptions = @@ -211,7 +211,7 @@ const char * gOutCertFileName = nullptr; const char * gOutKeyFileName = nullptr; uint32_t gValidDays = kCertValidDays_Undefined; struct tm gValidFrom; -AttCertStructConfig gCertConfig; +CertStructConfig gCertConfig; bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) { @@ -400,7 +400,7 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char return false; } break; -#endif +#endif // CHIP_CONFIG_INTERNAL_FLAG_GENERATE_DA_TEST_CASES default: PrintArgError("%s: Unhandled option: %s\n", progName, name); return false; diff --git a/src/tools/chip-cert/Cmd_GenCD.cpp b/src/tools/chip-cert/Cmd_GenCD.cpp index e0534c7f8f4e8d..6e85051d55086c 100644 --- a/src/tools/chip-cert/Cmd_GenCD.cpp +++ b/src/tools/chip-cert/Cmd_GenCD.cpp @@ -1115,7 +1115,6 @@ bool Cmd_GenCD(int argc, char * argv[]) fprintf(stderr, "Please specify the file name for the signed Certification Declaration using the --out option.\n"); return false; } - fprintf(stderr, "gSignedCDFileName = %s\n", gSignedCDFileName); if (strcmp(gSignedCDFileName, "-") != 0 && access(gSignedCDFileName, R_OK) == 0) { diff --git a/src/tools/chip-cert/Cmd_GenCert.cpp b/src/tools/chip-cert/Cmd_GenCert.cpp index 348bfc22efec93..9de1492200b1dc 100644 --- a/src/tools/chip-cert/Cmd_GenCert.cpp +++ b/src/tools/chip-cert/Cmd_GenCert.cpp @@ -59,6 +59,10 @@ OptionDef gCmdOptionDefs[] = { "out-format", kArgumentRequired, 'F' }, { "valid-from", kArgumentRequired, 'V' }, { "lifetime", kArgumentRequired, 'l' }, +#if CHIP_CONFIG_INTERNAL_FLAG_GENERATE_OP_CERT_TEST_CASES + { "ignore-error", kNoArgument, 'I' }, + { "error-type", kArgumentRequired, 'E' }, +#endif { } }; @@ -149,6 +153,68 @@ const char * const gCmdOptionHelp = " 4294967295 to indicate that certificate doesn't have well defined\n" " expiration date\n" "\n" +#if CHIP_CONFIG_INTERNAL_FLAG_GENERATE_OP_CERT_TEST_CASES + " -I, --ignore-error\n" + "\n" + " Ignore some input parameters error.\n" + " WARNING: This option makes it possible to circumvent attestation certificate\n" + " structure requirement. This is required for negative testing of the attestation flow.\n" + " Because of this it SHOULD NEVER BE ENABLED IN PRODUCTION BUILDS.\n" + "\n" + " -E, --error-type \n" + "\n" + " When specified injects specific error into the structure of generated attestation certificate.\n" + " Note that 'ignore-error' option MUST be specified for this error injection to take effect.\n" + " Supported error types that can be injected are:\n" + " no-error - No error to inject.\n" + " cert-oversized - Certificate size will exceed it's muximum supported size, which is\n" + " 400 bytes for the CHIP TLV encoded cert and 600 bytes for DER encoded cert.\n" + " cert-version - Certificate version will be set to v2 instead of required v3.\n" + " serial-number-missing - Certificate won't have required serialNumber field.\n" + " sig-algo - Use ecdsa-with-SHA1 signature algorithm instead of required ecdsa-with-SHA256.\n" + " issuer-missing - Certificate won't have required Issuer field.\n" + " validity-not-before-missing - Certificate won't have required validity not-before field.\n" + " validity-not-after-missing - Certificate won't have required validity not-after field.\n" + " validity-wrong - Certificate will have validity not-before and not-after values switched,\n" + " where not-before will have greater value than not-after.\n" + " subject-missing - Certificate won't have required Subject field.\n" + " subject-node-id-missing - Subject won't have NodeId attribute.\n" + " subject-node-id-invalid - Subject will include invalid NodeId value.\n" + " subject-node-id-twice - Subject will include two NodeId attributes.\n" + " subject-fabric-id-missing - Subject won't have FabricId attribute.\n" + " subject-fabric-id-invalid - Subject will include invalid FabricId value.\n" + " subject-fabric-id-twice - Subject will include two FabricId attributes.\n" + " subject-fabric-id-mismatch - The FabricId in the subject won't match FabricId in the issuer field.\n" + " subject-cat-invalid - Subject will include invalid CASE Authenticated Tag (CAT) value.\n" + " sig-curve - Use secp256k1 curve to generate certificate signature instead of\n" + " required secp256r1 (aka prime256v1).\n" + " publickey - Error will be injected in one of the bytes of the public key value.\n" + " required secp256r1 (aka prime256v1).\n" + " ext-basic-missing - Certificate won't have required Basic Constraint extension.\n" + " ext-basic-critical-missing - Basic Constraint extension won't have critical field.\n" + " ext-basic-critical-wrong - Basic Constraint extension will be marked as non-critical.\n" + " ext-basic-ca-missing - Basic Constraint extension won't have cA field.\n" + " ext-basic-ca-wrong - Basic Constraint extension cA field will be set to TRUE for DAC\n" + " and to FALSE for PAI and PAA.\n" + " ext-basic-pathlen-presence-wrong - Basic Constraint extension will include pathLen field for NOC.\n" + " ext-basic-pathlen0 - Basic Constraint extension pathLen field will be set to 0.\n" + " ext-basic-pathlen1 - Basic Constraint extension pathLen field will be set to 1.\n" + " ext-basic-pathlen2 - Basic Constraint extension pathLen field will be set to 2.\n" + " ext-key-usage-missing - Certificate won't have required Key Usage extension.\n" + " ext-key-usage-critical-missing - Key Usage extension won't have critical field.\n" + " ext-key-usage-critical-wrong - Key Usage extension will be marked as non-critical.\n" + " ext-key-usage-dig-sig - Key Usage extension digitalSignature flag won't be set for NOC\n" + " and will be set for ICAC/RCAC.\n" + " ext-key-usage-key-cert-sign - Key Usage extension keyCertSign flag will be set for NOC\n" + " and won't be set for ICAC/RCAC.\n" + " ext-key-usage-crl-sign - Key Usage extension cRLSign flag will be set for NOC\n" + " and won't set for ICAC/RCAC.\n" + " ext-akid-missing - Certificate won't have required Authority Key ID extension.\n" + " ext-skid-missing - Certificate won't have required Subject Key ID extension.\n" + " ext-extended-key-usage-missing - Certificate won't have required Extended Key Usage extension.\n" + " signature - Error will be injected in one of the bytes of the signature value.\n" + "\n" +#endif // CHIP_CONFIG_INTERNAL_FLAG_GENERATE_OP_CERT_TEST_CASES ; OptionSet gCmdOptions = @@ -189,6 +255,7 @@ uint32_t gValidDays = kCertValidDays_Undefined; FutureExtension gFutureExtensions[3] = { { 0, nullptr } }; uint8_t gFutureExtensionsCount = 0; struct tm gValidFrom; +CertStructConfig gCertConfig; bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) { @@ -237,12 +304,26 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char switch (gCertType) { case kCertType_Node: - if (!chip::IsOperationalNodeId(chip64bitAttr)) + if (gCertConfig.IsSubjectNodeIdValid() && !chip::IsOperationalNodeId(chip64bitAttr)) { PrintArgError("%s: Invalid value specified for chip node-id attribute: %s\n", progName, arg); return false; } - err = gSubjectDN.AddAttribute_MatterNodeId(chip64bitAttr); + if (gCertConfig.IsSubjectNodeIdPresent()) + { + if (gCertConfig.IsSubjectNodeIdValid()) + { + err = gSubjectDN.AddAttribute_MatterNodeId(chip64bitAttr); + } + else + { + err = gSubjectDN.AddAttribute_MatterNodeId(chip::kMaxOperationalNodeId + 10); + } + if ((err == CHIP_NO_ERROR) && gCertConfig.IsSubjectNodeIdRepeatsTwice()) + { + err = gSubjectDN.AddAttribute_MatterNodeId(chip64bitAttr + 1); + } + } break; case kCertType_FirmwareSigning: err = gSubjectDN.AddAttribute_MatterFirmwareSigningId(chip64bitAttr); @@ -295,7 +376,28 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char return false; } - err = gSubjectDN.AddAttribute_MatterFabricId(chip64bitAttr); + if (gCertConfig.IsSubjectFabricIdPresent()) + { + if (gCertConfig.IsSubjectFabricIdValid()) + { + if (gCertConfig.IsSubjectFabricIdMismatch()) + { + err = gSubjectDN.AddAttribute_MatterFabricId(chip64bitAttr + 1); + } + else + { + err = gSubjectDN.AddAttribute_MatterFabricId(chip64bitAttr); + } + } + else + { + err = gSubjectDN.AddAttribute_MatterFabricId(chip::kUndefinedFabricId); + } + if ((err == CHIP_NO_ERROR) && gCertConfig.IsSubjectFabricIdRepeatsTwice()) + { + err = gSubjectDN.AddAttribute_MatterFabricId(chip64bitAttr + 10); + } + } if (err != CHIP_NO_ERROR) { fprintf(stderr, "Failed to add Fabric Id attribute to the subject DN: %s\n", chip::ErrorStr(err)); @@ -377,6 +479,170 @@ bool HandleOption(const char * progName, OptionSet * optSet, int id, const char return false; } break; +#if CHIP_CONFIG_INTERNAL_FLAG_GENERATE_OP_CERT_TEST_CASES + case 'I': + gCertConfig.EnableErrorTestCase(); + break; + case 'E': + if (strcmp(arg, "cert-oversized") == 0) + { + gCertConfig.SetCertOversized(); + } + else if (strcmp(arg, "cert-version") == 0) + { + gCertConfig.SetCertVersionWrong(); + } + else if (strcmp(arg, "serial-number-missing") == 0) + { + gCertConfig.SetSerialNumberMissing(); + } + else if (strcmp(arg, "sig-algo") == 0) + { + gCertConfig.SetSigAlgoWrong(); + } + else if (strcmp(arg, "issuer-missing") == 0) + { + gCertConfig.SetIssuerMissing(); + } + else if (strcmp(arg, "validity-not-before-missing") == 0) + { + gCertConfig.SetValidityNotBeforeMissing(); + } + else if (strcmp(arg, "validity-not-after-missing") == 0) + { + gCertConfig.SetValidityNotAfterMissing(); + } + else if (strcmp(arg, "validity-wrong") == 0) + { + gCertConfig.SetValidityWrong(); + } + else if (strcmp(arg, "subject-missing") == 0) + { + gCertConfig.SetSubjectMissing(); + } + else if (strcmp(arg, "subject-node-id-missing") == 0) + { + gCertConfig.SetSubjectNodeIdMissing(); + } + else if (strcmp(arg, "subject-node-id-invalid") == 0) + { + gCertConfig.SetSubjectNodeIdInvalid(); + } + else if (strcmp(arg, "subject-node-id-twice") == 0) + { + gCertConfig.SetSubjectNodeIdTwice(); + } + else if (strcmp(arg, "subject-fabric-id-missing") == 0) + { + gCertConfig.SetSubjectFabricIdMissing(); + } + else if (strcmp(arg, "subject-fabric-id-invalid") == 0) + { + gCertConfig.SetSubjectFabricIdInvalid(); + } + else if (strcmp(arg, "subject-fabric-id-twice") == 0) + { + gCertConfig.SetSubjectFabricIdTwice(); + } + else if (strcmp(arg, "subject-fabric-id-mismatch") == 0) + { + gCertConfig.SetSubjectFabricIdMismatch(); + } + else if (strcmp(arg, "subject-cat-invalid") == 0) + { + gCertConfig.SetSubjectCATInvalid(); + } + else if (strcmp(arg, "sig-curve") == 0) + { + gCertConfig.SetSigCurveWrong(); + } + else if (strcmp(arg, "publickey") == 0) + { + gCertConfig.SetPublicKeyError(); + } + else if (strcmp(arg, "ext-basic-missing") == 0) + { + gCertConfig.SetExtensionBasicMissing(); + } + else if (strcmp(arg, "ext-basic-critical-missing") == 0) + { + gCertConfig.SetExtensionBasicCriticalMissing(); + } + else if (strcmp(arg, "ext-basic-critical-wrong") == 0) + { + gCertConfig.SetExtensionBasicCriticalWrong(); + } + else if (strcmp(arg, "ext-basic-ca-missing") == 0) + { + gCertConfig.SetExtensionBasicCAMissing(); + } + else if (strcmp(arg, "ext-basic-ca-wrong") == 0) + { + gCertConfig.SetExtensionBasicCAWrong(); + } + else if (strcmp(arg, "ext-basic-pathlen-presence-wrong") == 0) + { + gCertConfig.SetExtensionBasicPathLenPresenceWrong(); + } + else if (strcmp(arg, "ext-basic-pathlen0") == 0) + { + gCertConfig.SetExtensionBasicPathLen0(); + } + else if (strcmp(arg, "ext-basic-pathlen1") == 0) + { + gCertConfig.SetExtensionBasicPathLen1(); + } + else if (strcmp(arg, "ext-basic-pathlen2") == 0) + { + gCertConfig.SetExtensionBasicPathLen2(); + } + else if (strcmp(arg, "ext-key-usage-missing") == 0) + { + gCertConfig.SetExtensionKeyUsageMissing(); + } + else if (strcmp(arg, "ext-key-usage-critical-missing") == 0) + { + gCertConfig.SetExtensionKeyUsageCriticalMissing(); + } + else if (strcmp(arg, "ext-key-usage-critical-wrong") == 0) + { + gCertConfig.SetExtensionKeyUsageCriticalWrong(); + } + else if (strcmp(arg, "ext-key-usage-dig-sig") == 0) + { + gCertConfig.SetExtensionKeyUsageDigitalSigWrong(); + } + else if (strcmp(arg, "ext-key-usage-key-cert-sign") == 0) + { + gCertConfig.SetExtensionKeyUsageKeyCertSignWrong(); + } + else if (strcmp(arg, "ext-key-usage-crl-sign") == 0) + { + gCertConfig.SetExtensionKeyUsageCRLSignWrong(); + } + else if (strcmp(arg, "ext-akid-missing") == 0) + { + gCertConfig.SetExtensionAKIDMissing(); + } + else if (strcmp(arg, "ext-skid-missing") == 0) + { + gCertConfig.SetExtensionSKIDMissing(); + } + else if (strcmp(arg, "ext-extended-key-usage-missing") == 0) + { + gCertConfig.SetExtensionExtendedKeyUsageMissing(); + } + else if (strcmp(arg, "signature") == 0) + { + gCertConfig.SetSignatureError(); + } + else if (strcmp(arg, "no-error") != 0) + { + PrintArgError("%s: Invalid value specified for the error type: %s\n", progName, arg); + return false; + } + break; +#endif // CHIP_CONFIG_INTERNAL_FLAG_GENERATE_OP_CERT_TEST_CASES default: PrintArgError("%s: Unhandled option: %s\n", progName, name); return false; @@ -394,6 +660,10 @@ bool Cmd_GenCert(int argc, char * argv[]) uint8_t certType = kCertType_NotSpecified; std::unique_ptr newCert(X509_new(), &X509_free); std::unique_ptr newKey(EVP_PKEY_new(), &EVP_PKEY_free); + std::unique_ptr caCert(X509_new(), &X509_free); + std::unique_ptr caKey(EVP_PKEY_new(), &EVP_PKEY_free); + X509 * caCertPtr = nullptr; + EVP_PKEY * caKeyPtr = nullptr; { time_t now = time(nullptr); @@ -412,22 +682,57 @@ bool Cmd_GenCert(int argc, char * argv[]) res = ParseArgs(CMD_NAME, argc, argv, gCmdOptionSets); VerifyTrueOrExit(res); + if (gCertConfig.IsErrorTestCaseEnabled()) + { + fprintf(stderr, + "WARNING get-cert: The ignor-error option is set. This option makes it possible to generate invalid " + "certificates.\n"); + } + if (gSubjectDN.IsEmpty()) { fprintf(stderr, "Please specify the subject DN attributes.\n"); ExitNow(res = false); } - err = gSubjectDN.GetCertType(certType); - if (err != CHIP_NO_ERROR) + if (gCertConfig.IsCertOversized()) { - fprintf(stderr, "Invalid certificate subject attribute specified: %s\n", chip::ErrorStr(err)); - ExitNow(res = false); + // Largest CN attribute supported by ASN1 library is 64 bytes. + const char * cn = "Common Name Subject DN Attribute for the Oversize Error Testcase"; + for (int i = 0; i < 3; i++) + { + err = gSubjectDN.AddAttribute_CommonName(chip::CharSpan::fromCharString(cn), false); + if (err != CHIP_NO_ERROR) + { + fprintf(stderr, "Failed to add large-size CN attribute for Oversized testcase: %s\n", chip::ErrorStr(err)); + ExitNow(res = false); + } + } } - if (certType != gCertType) + + if (!gCertConfig.IsSubjectCATValid()) { - fprintf(stderr, "Please specify certificate type that matches subject DN attributes.\n"); - ExitNow(res = false); + err = gSubjectDN.AddAttribute_MatterCASEAuthTag(0xABCD0000); + if (err != CHIP_NO_ERROR) + { + fprintf(stderr, "Failed to add Invalid CAT to the Subject DN: %s\n", chip::ErrorStr(err)); + ExitNow(res = false); + } + } + + if (!gCertConfig.IsErrorTestCaseEnabled()) + { + err = gSubjectDN.GetCertType(certType); + if (err != CHIP_NO_ERROR) + { + fprintf(stderr, "Invalid certificate subject attribute specified: %s\n", chip::ErrorStr(err)); + ExitNow(res = false); + } + if (certType != gCertType) + { + fprintf(stderr, "Please specify certificate type that matches subject DN attributes.\n"); + ExitNow(res = false); + } } if (gCACertFileName == nullptr && !gSelfSign) @@ -500,35 +805,58 @@ bool Cmd_GenCert(int argc, char * argv[]) } else { - res = GenerateKeyPair(newKey.get()); - VerifyTrueOrExit(res); + if (gCertConfig.IsSigCurveWrong()) + { + res = GenerateKeyPair_Secp256k1(newKey.get()); + VerifyTrueOrExit(res); + } + else + { + res = GenerateKeyPair(newKey.get()); + VerifyTrueOrExit(res); + } } if (gSelfSign) { - res = MakeCert(gCertType, &gSubjectDN, newCert.get(), newKey.get(), gValidFrom, gValidDays, gPathLengthConstraint, - gFutureExtensions, gFutureExtensionsCount, newCert.get(), newKey.get()); - VerifyTrueOrExit(res); + caCertPtr = newCert.get(); + caKeyPtr = newKey.get(); } else { - std::unique_ptr caCert(X509_new(), &X509_free); - std::unique_ptr caKey(EVP_PKEY_new(), &EVP_PKEY_free); - res = ReadCert(gCACertFileName, caCert.get()); VerifyTrueOrExit(res); res = ReadKey(gCAKeyFileName, caKey.get()); VerifyTrueOrExit(res); - res = MakeCert(gCertType, &gSubjectDN, caCert.get(), caKey.get(), gValidFrom, gValidDays, gPathLengthConstraint, - gFutureExtensions, gFutureExtensionsCount, newCert.get(), newKey.get()); - VerifyTrueOrExit(res); + caCertPtr = caCert.get(); + caKeyPtr = caKey.get(); } - res = WriteCert(gOutCertFileName, newCert.get(), gOutCertFormat); + res = MakeCert(gCertType, &gSubjectDN, caCertPtr, caKeyPtr, gValidFrom, gValidDays, gPathLengthConstraint, gFutureExtensions, + gFutureExtensionsCount, newCert.get(), newKey.get(), gCertConfig); VerifyTrueOrExit(res); + if (gCertConfig.IsErrorTestCaseEnabled() && + (gOutCertFormat == kCertFormat_Chip_Raw || gOutCertFormat == kCertFormat_Chip_Base64)) + { + static constexpr uint32_t kExtraBufferLengthForOvesizedCert = 300; + uint8_t chipCertBuf[kMaxCHIPCertLength + kExtraBufferLengthForOvesizedCert]; + chip::MutableByteSpan chipCert(chipCertBuf); + err = MakeCertChipTLV(gCertType, &gSubjectDN, caCertPtr, caKeyPtr, gValidFrom, gValidDays, gPathLengthConstraint, + gFutureExtensions, gFutureExtensionsCount, newCert.get(), newKey.get(), gCertConfig, chipCert); + VerifyTrueOrExit(err == CHIP_NO_ERROR); + + res = WriteChipCert(gOutCertFileName, chipCert, gOutCertFormat); + VerifyTrueOrExit(res); + } + else + { + res = WriteCert(gOutCertFileName, newCert.get(), gOutCertFormat); + VerifyTrueOrExit(res); + } + if (gOutKeyFileName != nullptr) { res = WritePrivateKey(gOutKeyFileName, newKey.get(), gOutKeyFormat); diff --git a/src/tools/chip-cert/chip-cert.h b/src/tools/chip-cert/chip-cert.h index 21c24b15cc790b..681e9ad01d664c 100644 --- a/src/tools/chip-cert/chip-cert.h +++ b/src/tools/chip-cert/chip-cert.h @@ -71,6 +71,10 @@ using chip::ASN1::OID; #define CHIP_CONFIG_INTERNAL_FLAG_GENERATE_DA_TEST_CASES CHIP_CONFIG_TEST #endif +#ifndef CHIP_CONFIG_INTERNAL_FLAG_GENERATE_OP_CERT_TEST_CASES +#define CHIP_CONFIG_INTERNAL_FLAG_GENERATE_OP_CERT_TEST_CASES CHIP_CONFIG_TEST +#endif + #define COPYRIGHT_STRING \ "Copyright (c) 2021-2022 Project CHIP Authors" \ "Copyright (c) 2019 Google LLC." \ @@ -117,13 +121,13 @@ struct FutureExtension const char * info; }; -/** Attestation Certificate Error Flags +/** Certificate Error Flags * * By default all methods (if none of the class setters were used) return valid - * attestation certificate configuration parameter as described in the spec. + * certificate configuration parameter as described in the spec. * These parameters can be modified to inject errors into certificate structure. */ -class AttCertStructConfig +class CertStructConfig { public: void EnableErrorTestCase() { mEnabled = true; } @@ -132,6 +136,7 @@ class AttCertStructConfig void SetSubjectVIDMismatch() { mFlags.Set(CertErrorFlags::kSubjectVIDMismatch); } void SetSubjectPIDMismatch() { mFlags.Set(CertErrorFlags::kSubjectPIDMismatch); } void SetSigCurveWrong() { mFlags.Set(CertErrorFlags::kSigCurve); } + void SetPublicKeyError() { mFlags.Set(CertErrorFlags::kPublicKey); } void SetExtensionBasicMissing() { mFlags.Set(CertErrorFlags::kExtBasicMissing); } void SetExtensionBasicCriticalMissing() { mFlags.Set(CertErrorFlags::kExtBasicCriticalMissing); } void SetExtensionBasicCriticalWrong() { mFlags.Set(CertErrorFlags::kExtBasicCriticalWrong); } @@ -152,16 +157,42 @@ class AttCertStructConfig void SetExtensionExtendedKeyUsagePresent() { mFlags.Set(CertErrorFlags::kExtExtendedKeyUsage); } void SetExtensionAuthorityInfoAccessPresent() { mFlags.Set(CertErrorFlags::kExtAuthorityInfoAccess); } void SetExtensionSubjectAltNamePresent() { mFlags.Set(CertErrorFlags::kExtSubjectAltName); } + void SetSignatureError() { mFlags.Set(CertErrorFlags::kSignature); } + + void SetCertOversized() { mFlags.Set(CertErrorFlags::kCertOversized); } + void SetSerialNumberMissing() { mFlags.Set(CertErrorFlags::kSerialNumberMissing); } + void SetIssuerMissing() { mFlags.Set(CertErrorFlags::kIssuerMissing); } + void SetValidityNotBeforeMissing() { mFlags.Set(CertErrorFlags::kValidityNotBeforeMissing); } + void SetValidityNotAfterMissing() { mFlags.Set(CertErrorFlags::kValidityNotAfterMissing); } + void SetValidityWrong() { mFlags.Set(CertErrorFlags::kValidityWrong); } + void SetSubjectMissing() { mFlags.Set(CertErrorFlags::kSubjectMissing); } + void SetSubjectNodeIdMissing() { mFlags.Set(CertErrorFlags::kSubjectNodeIdMissing); } + void SetSubjectNodeIdInvalid() { mFlags.Set(CertErrorFlags::kSubjectNodeIdInvalid); } + void SetSubjectNodeIdTwice() { mFlags.Set(CertErrorFlags::kSubjectNodeIdTwice); } + void SetSubjectFabricIdMissing() { mFlags.Set(CertErrorFlags::kSubjectFabricIdMissing); } + void SetSubjectFabricIdInvalid() { mFlags.Set(CertErrorFlags::kSubjectFabricIdInvalid); } + void SetSubjectFabricIdTwice() { mFlags.Set(CertErrorFlags::kSubjectFabricIdTwice); } + void SetSubjectFabricIdMismatch() { mFlags.Set(CertErrorFlags::kSubjectFabricIdMismatch); } + void SetSubjectCATInvalid() { mFlags.Set(CertErrorFlags::kSubjectCATInvalid); } + void SetExtensionExtendedKeyUsageMissing() { mFlags.Set(CertErrorFlags::kExtExtendedKeyUsageMissing); } bool IsErrorTestCaseEnabled() { return mEnabled; } int GetCertVersion() { return (mEnabled && mFlags.Has(CertErrorFlags::kCertVersion)) ? 1 : 2; } - const EVP_MD * GetSignatureAlgorithm() + const EVP_MD * GetSignatureAlgorithmDER() { return (mEnabled && mFlags.Has(CertErrorFlags::kSigAlgo)) ? EVP_sha1() : EVP_sha256(); } + uint8_t GetSignatureAlgorithmTLVEnum() + { + return (mEnabled && mFlags.Has(CertErrorFlags::kSigAlgo)) ? 0x02 + : GetOIDEnum(chip::ASN1:: + + kOID_SigAlgo_ECDSAWithSHA256); + } bool IsSubjectVIDMismatch() { return (mEnabled && mFlags.Has(CertErrorFlags::kSubjectVIDMismatch)); } bool IsSubjectPIDMismatch() { return (mEnabled && mFlags.Has(CertErrorFlags::kSubjectPIDMismatch)); } bool IsSigCurveWrong() { return (mEnabled && mFlags.Has(CertErrorFlags::kSigCurve)); } + bool IsPublicKeyError() { return (mEnabled && mFlags.Has(CertErrorFlags::kPublicKey)); } bool IsExtensionBasicPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kExtBasicMissing)); } bool IsExtensionBasicCriticalPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kExtBasicCriticalMissing)); } bool IsExtensionBasicCritical() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kExtBasicCriticalWrong)); } @@ -197,6 +228,43 @@ class AttCertStructConfig } return 0; } + bool IsExtensionBasicPathLenPresent() + { + return (mEnabled && + (mFlags.Has(CertErrorFlags::kExtBasicPathLenWrong) || mFlags.Has(CertErrorFlags::kExtBasicPathLen0) || + mFlags.Has(CertErrorFlags::kExtBasicPathLen1) || mFlags.Has(CertErrorFlags::kExtBasicPathLen2))); + } + int GetExtensionBasicPathLenValue(uint8_t & certType) + { + if (mFlags.Has(CertErrorFlags::kExtBasicPathLen0)) + { + return 0; + } + if (mFlags.Has(CertErrorFlags::kExtBasicPathLen1)) + { + return 1; + } + if (mFlags.Has(CertErrorFlags::kExtBasicPathLen2)) + { + return 2; + } + if (mFlags.Has(CertErrorFlags::kExtBasicPathLenWrong)) + { + if (certType == chip::Credentials::kCertType_Node) + { + return 2; + } + if (certType == chip::Credentials::kCertType_ICA) + { + return 1; + } + if (certType == chip::Credentials::kCertType_Root) + { + return 0; + } + } + return 0; + } bool IsExtensionKeyUsagePresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kExtKeyUsageMissing)); } bool IsExtensionKeyUsageCriticalPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kExtKeyUsageCriticalMissing)); } bool IsExtensionKeyUsageCritical() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kExtKeyUsageCriticalWrong)); } @@ -208,35 +276,75 @@ class AttCertStructConfig bool IsExtensionExtendedKeyUsagePresent() { return (mEnabled && mFlags.Has(CertErrorFlags::kExtExtendedKeyUsage)); } bool IsExtensionAuthorityInfoAccessPresent() { return (mEnabled && mFlags.Has(CertErrorFlags::kExtAuthorityInfoAccess)); } bool IsExtensionSubjectAltNamePresent() { return (mEnabled && mFlags.Has(CertErrorFlags::kExtSubjectAltName)); } + bool IsSignatureError() { return (mEnabled && mFlags.Has(CertErrorFlags::kSignature)); } + + bool IsCertOversized() { return (mEnabled && mFlags.Has(CertErrorFlags::kCertOversized)); } + bool IsSerialNumberPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kSerialNumberMissing)); } + bool IsIssuerPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kIssuerMissing)); } + bool IsValidityNotBeforePresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kValidityNotBeforeMissing)); } + bool IsValidityNotAfterPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kValidityNotAfterMissing)); } + bool IsValidityCorrect() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kValidityWrong)); } + bool IsSubjectPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kSubjectMissing)); } + bool IsSubjectNodeIdPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kSubjectNodeIdMissing)); } + bool IsSubjectNodeIdValid() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kSubjectNodeIdInvalid)); } + bool IsSubjectNodeIdRepeatsTwice() { return (mEnabled && mFlags.Has(CertErrorFlags::kSubjectNodeIdTwice)); } + bool IsSubjectFabricIdPresent() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kSubjectFabricIdMissing)); } + bool IsSubjectFabricIdValid() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kSubjectFabricIdInvalid)); } + bool IsSubjectFabricIdRepeatsTwice() { return (mEnabled && mFlags.Has(CertErrorFlags::kSubjectFabricIdTwice)); } + bool IsSubjectFabricIdMismatch() { return (mEnabled && mFlags.Has(CertErrorFlags::kSubjectFabricIdMismatch)); } + bool IsSubjectCATValid() { return (!mEnabled || !mFlags.Has(CertErrorFlags::kSubjectCATInvalid)); } + bool IsExtensionExtendedKeyUsageMissing() { return (mEnabled && mFlags.Has(CertErrorFlags::kExtExtendedKeyUsageMissing)); } + + static constexpr uint8_t kPublicKeyErrorByte = 20; private: - enum class CertErrorFlags : uint32_t + enum class CertErrorFlags : uint64_t { - kCertVersion = 0x00000001, - kSigAlgo = 0x00000002, - kSubjectVIDMismatch = 0x00000004, - kSubjectPIDMismatch = 0x00000008, - kSigCurve = 0x00000010, - kExtBasicMissing = 0x00000020, - kExtBasicCriticalMissing = 0x00000040, - kExtBasicCriticalWrong = 0x00000080, - kExtBasicCAMissing = 0x00000100, - kExtBasicCAWrong = 0x00000200, - kExtBasicPathLenWrong = 0x00000400, - kExtBasicPathLen0 = 0x00000800, - kExtBasicPathLen1 = 0x00001000, - kExtBasicPathLen2 = 0x00002000, - kExtKeyUsageMissing = 0x00004000, - kExtKeyUsageCriticalMissing = 0x00008000, - kExtKeyUsageCriticalWrong = 0x00010000, - kExtKeyUsageDigSig = 0x00020000, - kExtKeyUsageKeyCertSign = 0x00040000, - kExtKeyUsageCRLSign = 0x00080000, - kExtAKIDMissing = 0x00100000, - kExtSKIDMissing = 0x00200000, - kExtExtendedKeyUsage = 0x00400000, - kExtAuthorityInfoAccess = 0x00800000, - kExtSubjectAltName = 0x01000000, + kCertVersion = 0x0000000000000001, + kSigAlgo = 0x0000000000000002, + kSubjectVIDMismatch = 0x0000000000000004, // DA specific + kSubjectPIDMismatch = 0x0000000000000008, // DA specific + kSigCurve = 0x0000000000000010, + kPublicKey = 0x0000000000000020, + kExtBasicMissing = 0x0000000000000040, + kExtBasicCriticalMissing = 0x0000000000000080, + kExtBasicCriticalWrong = 0x0000000000000100, + kExtBasicCAMissing = 0x0000000000000200, + kExtBasicCAWrong = 0x0000000000000400, + kExtBasicPathLenWrong = 0x0000000000000800, + kExtBasicPathLen0 = 0x0000000000001000, + kExtBasicPathLen1 = 0x0000000000002000, + kExtBasicPathLen2 = 0x0000000000004000, + kExtKeyUsageMissing = 0x0000000000008000, + kExtKeyUsageCriticalMissing = 0x0000000000010000, + kExtKeyUsageCriticalWrong = 0x0000000000020000, + kExtKeyUsageDigSig = 0x0000000000040000, + kExtKeyUsageKeyCertSign = 0x0000000000080000, + kExtKeyUsageCRLSign = 0x0000000000100000, + kExtAKIDMissing = 0x0000000000200000, + kExtSKIDMissing = 0x0000000000400000, + kExtExtendedKeyUsage = 0x0000000000800000, // DA specific + kExtAuthorityInfoAccess = 0x0000000001000000, // DA specific + kExtSubjectAltName = 0x0000000002000000, // DA specific + kSignature = 0x0000000004000000, + + // Op Cert Specific Flags: + kCertOversized = 0x0000000100000000, + kSerialNumberMissing = 0x0000000200000000, + kIssuerMissing = 0x0000000400000000, + kValidityNotBeforeMissing = 0x0000000800000000, + kValidityNotAfterMissing = 0x0000001000000000, + kValidityWrong = 0x0000002000000000, + kSubjectMissing = 0x0000004000000000, + kSubjectNodeIdMissing = 0x0000008000000000, + kSubjectNodeIdInvalid = 0x0000010000000000, + kSubjectNodeIdTwice = 0x0000020000000000, + kSubjectFabricIdMissing = 0x0000040000000000, + kSubjectFabricIdInvalid = 0x0000080000000000, + kSubjectFabricIdTwice = 0x0000100000000000, + kSubjectFabricIdMismatch = 0x0000200000000000, + kSubjectCATInvalid = 0x0000400000000000, + kExtExtendedKeyUsageMissing = 0x0000800000000000, }; bool mEnabled = false; @@ -246,7 +354,9 @@ class AttCertStructConfig class ToolChipDN : public chip::Credentials::ChipDN { public: - bool SetCertSubjectDN(X509 * cert) const; + bool SetCertName(X509_NAME * name) const; + bool SetCertSubjectDN(X509 * cert) const { return SetCertName(X509_get_subject_name(cert)); }; + bool SetCertIssuerDN(X509 * cert) const { return SetCertName(X509_get_issuer_name(cert)); }; bool HasAttr(chip::ASN1::OID oid) const; void PrintDN(FILE * file, const char * name) const; }; @@ -268,15 +378,20 @@ extern bool LoadChipCert(const char * fileName, bool isTrused, chip::Credentials chip::MutableByteSpan & chipCert); extern bool WriteCert(const char * fileName, X509 * cert, CertFormat certFmt); +extern bool WriteChipCert(const char * fileName, const chip::ByteSpan & cert, CertFormat certFmt); extern bool MakeCert(uint8_t certType, const ToolChipDN * subjectDN, X509 * caCert, EVP_PKEY * caKey, const struct tm & validFrom, uint32_t validDays, int pathLen, const FutureExtension * futureExts, uint8_t futureExtsCount, X509 * newCert, - EVP_PKEY * newKey); + EVP_PKEY * newKey, CertStructConfig & certConfig); +extern CHIP_ERROR MakeCertChipTLV(uint8_t certType, const ToolChipDN * subjectDN, X509 * caCert, EVP_PKEY * caKey, + const struct tm & validFrom, uint32_t validDays, int pathLen, const FutureExtension * futureExts, + uint8_t futureExtsCount, X509 * x509Cert, EVP_PKEY * newKey, CertStructConfig & certConfig, + chip::MutableByteSpan & chipCert); extern bool ResignCert(X509 * cert, X509 * caCert, EVP_PKEY * caKey); extern bool MakeAttCert(AttCertType attCertType, const char * subjectCN, uint16_t subjectVID, uint16_t subjectPID, bool encodeVIDandPIDasCN, X509 * caCert, EVP_PKEY * caKey, const struct tm & validFrom, uint32_t validDays, - X509 * newCert, EVP_PKEY * newKey, AttCertStructConfig & certConfig); + X509 * newCert, EVP_PKEY * newKey, CertStructConfig & certConfig); extern bool GenerateKeyPair(EVP_PKEY * key); extern bool GenerateKeyPair_Secp256k1(EVP_PKEY * key); extern bool ReadKey(const char * fileName, EVP_PKEY * key, bool ignorErrorIfUnsupportedCurve = false);