diff --git a/src/credentials/BUILD.gn b/src/credentials/BUILD.gn index 80e4e06d818c1c..b0d03ee1c3b44b 100644 --- a/src/credentials/BUILD.gn +++ b/src/credentials/BUILD.gn @@ -25,6 +25,7 @@ static_library("credentials") { "CHIPCertToX509.cpp", "CHIPOperationalCredentials.cpp", "CHIPOperationalCredentials.h", + "GenerateChipX509Cert.cpp", ] cflags = [ "-Wconversion" ] diff --git a/src/credentials/CHIPCert.h b/src/credentials/CHIPCert.h index 497b796d2ea33c..c8295fd289d0cb 100644 --- a/src/credentials/CHIPCert.h +++ b/src/credentials/CHIPCert.h @@ -641,6 +641,88 @@ CHIP_ERROR ConvertX509CertToChipCert(const uint8_t * x509Cert, uint32_t x509Cert CHIP_ERROR ConvertChipCertToX509Cert(const uint8_t * chipCert, uint32_t chipCertLen, uint8_t * x509CertBuf, uint32_t x509CertBufSize, uint32_t & x509CertLen); +/** + * @brief Generate a standard X.509 DER encoded certificate using provided CHIP certificate and signing key + * + * @param chipCert Buffer containing CHIP certificate. + * @param chipCertLen The length of the CHIP certificate. + * @param keypair The certificate signing key + * @param x509CertBuf Buffer to store signed certificate in X.509 DER format. + * @param x509CertBufSize The size of the buffer to store converted certificate. + * @param x509CertLen The length of the converted certificate. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR GenerateSignedX509CertFromChipCert(const uint8_t * chipCert, uint32_t chipCertLen, Crypto::P256Keypair & keypair, + uint8_t * x509CertBuf, uint32_t x509CertBufSize, uint32_t & x509CertLen); + +// TODO: Add support for Authentication Tag Attribute +struct X509CertRequestParams +{ + int64_t SerialNumber; + uint64_t Issuer; + uint32_t ValidityStart; + uint32_t ValidityEnd; + bool HasFabricID; + uint64_t FabricID; + bool HasNodeID; + uint64_t NodeID; +}; + +enum CertificateIssuerLevel +{ + kIssuerIsRootCA, + kIssuerIsIntermediateCA, +}; + +/** + * @brief Generate a new X.509 DER encoded Root CA certificate + * + * @param requestParams Certificate request parameters. + * @param issuerKeypair The certificate signing key + * @param x509CertBuf Buffer to store signed certificate in X.509 DER format. + * @param x509CertBufSize The size of the buffer to store converted certificate. + * @param x509CertLen The length of the converted certificate. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR NewRootX509Cert(const X509CertRequestParams & requestParams, Crypto::P256Keypair & issuerKeypair, uint8_t * x509CertBuf, + uint32_t x509CertBufSize, uint32_t & x509CertLen); + +/** + * @brief Generate a new X.509 DER encoded Intermediate CA certificate + * + * @param requestParams Certificate request parameters. + * @param subject The requested subject ID + * @param subjectPubkey The public key of subject + * @param issuerKeypair The certificate signing key + * @param x509CertBuf Buffer to store signed certificate in X.509 DER format. + * @param x509CertBufSize The size of the buffer to store converted certificate. + * @param x509CertLen The length of the converted certificate. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR NewICAX509Cert(const X509CertRequestParams & requestParams, uint64_t subject, + const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair, uint8_t * x509CertBuf, + uint32_t x509CertBufSize, uint32_t & x509CertLen); + +/** + * @brief Generate a new X.509 DER encoded Node operational certificate + * + * @param requestParams Certificate request parameters. + * @param issuerLevel Indicates if the issuer is a root CA or an intermediate CA + * @param subjectPubkey The public key of subject + * @param issuerKeypair The certificate signing key + * @param x509CertBuf Buffer to store signed certificate in X.509 DER format. + * @param x509CertBufSize The size of the buffer to store converted certificate. + * @param x509CertLen The length of the converted certificate. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR NewNodeOperationalX509Cert(const X509CertRequestParams & requestParams, CertificateIssuerLevel issuerLevel, + const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair, + uint8_t * x509CertBuf, uint32_t x509CertBufSize, uint32_t & x509CertLen); + /** * @brief * Convert a certificate date/time (in the form of an ASN.1 universal time structure) into a CHIP Epoch UTC time. diff --git a/src/credentials/GenerateChipX509Cert.cpp b/src/credentials/GenerateChipX509Cert.cpp new file mode 100644 index 00000000000000..152cd3c8f679e7 --- /dev/null +++ b/src/credentials/GenerateChipX509Cert.cpp @@ -0,0 +1,466 @@ +/* + * + * Copyright (c) 2021 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 methods for generating CHIP X.509 certificate. + * + */ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace Credentials { + +using namespace chip::ASN1; +using namespace chip::Protocols; + +namespace { + +struct ChipDNParams +{ + OID AttrOID; + uint64_t Value; +}; + +enum IsCACert +{ + kCACert, + kNotCACert, +}; + +constexpr uint8_t kSHA1_Hash_Langth = 20; + +CHIP_ERROR EncodeSubjectPublicKeyInfo(const Crypto::P256PublicKey & pubkey, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_SEQUENCE + { + ASN1_START_SEQUENCE + { + ASN1_ENCODE_OBJECT_ID(kOID_PubKeyAlgo_ECPublicKey); + ASN1_ENCODE_OBJECT_ID(kOID_EllipticCurve_prime256v1); + } + ASN1_END_SEQUENCE; + + ReturnErrorOnFailure(writer.PutBitString(0, pubkey, static_cast(pubkey.Length()))); + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeAuthorityKeyIdentifierExtension(const Crypto::P256PublicKey & pubkey, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_SEQUENCE + { + OID extensionOID = GetOID(kOIDCategory_Extension, static_cast(kTag_AuthorityKeyIdentifier)); + + ASN1_ENCODE_OBJECT_ID(extensionOID); + + ASN1_START_OCTET_STRING_ENCAPSULATED + { + ASN1_START_SEQUENCE + { + uint8_t keyid[kSHA1_Hash_Langth]; + ReturnErrorOnFailure(Crypto::Hash_SHA1(pubkey, pubkey.Length(), keyid)); + + ReturnErrorOnFailure( + writer.PutOctetString(kASN1TagClass_ContextSpecific, 0, keyid, static_cast(sizeof(keyid)))); + } + ASN1_END_SEQUENCE; + } + ASN1_END_ENCAPSULATED; + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeSubjectKeyIdentifierExtension(const Crypto::P256PublicKey & pubkey, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_SEQUENCE + { + OID extensionOID = GetOID(kOIDCategory_Extension, static_cast(kTag_SubjectKeyIdentifier)); + + ASN1_ENCODE_OBJECT_ID(extensionOID); + + ASN1_START_OCTET_STRING_ENCAPSULATED + { + uint8_t keyid[kSHA1_Hash_Langth]; + ReturnErrorOnFailure(Crypto::Hash_SHA1(pubkey, pubkey.Length(), keyid)); + + ReturnErrorOnFailure(writer.PutOctetString(keyid, static_cast(sizeof(keyid)))); + } + ASN1_END_ENCAPSULATED; + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeKeyUsageExtension(uint16_t keyUsageBits, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_SEQUENCE + { + OID extensionOID = GetOID(kOIDCategory_Extension, static_cast(kTag_KeyUsage)); + + ASN1_ENCODE_OBJECT_ID(extensionOID); + + // KeyUsage extension MUST be marked as critical. + ASN1_ENCODE_BOOLEAN(true); + ASN1_START_OCTET_STRING_ENCAPSULATED { ASN1_ENCODE_BIT_STRING(keyUsageBits); } + ASN1_END_ENCAPSULATED; + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeIsCAExtension(IsCACert isCA, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_SEQUENCE + { + OID extensionOID = GetOID(kOIDCategory_Extension, static_cast(kTag_BasicConstraints)); + + ASN1_ENCODE_OBJECT_ID(extensionOID); + + // BasicConstraints extension MUST be marked as critical. + ASN1_ENCODE_BOOLEAN(true); + + ASN1_START_OCTET_STRING_ENCAPSULATED + { + ASN1_START_SEQUENCE + { + // cA BOOLEAN + if (isCA == kCACert) + { + // Encode the boolean only if isCA is true + ASN1_ENCODE_BOOLEAN(true); + } + } + ASN1_END_SEQUENCE; + } + ASN1_END_ENCAPSULATED; + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeCASpecificExtensions(ASN1Writer & writer) +{ + ReturnErrorOnFailure(EncodeIsCAExtension(kCACert, writer)); + + uint16_t keyUsageBits = static_cast(KeyUsageFlags::kKeyCertSign) | static_cast(KeyUsageFlags::kCRLSign); + + ReturnErrorOnFailure(EncodeKeyUsageExtension(keyUsageBits, writer)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR EncodeNOCSpecificExtensions(ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + uint16_t keyUsageBits = + static_cast(KeyUsageFlags::kDigitalSignature) | static_cast(KeyUsageFlags::kKeyEncipherment); + + ReturnErrorOnFailure(EncodeIsCAExtension(kNotCACert, writer)); + ReturnErrorOnFailure(EncodeKeyUsageExtension(keyUsageBits, writer)); + + ASN1_START_SEQUENCE + { + OID extensionOID = GetOID(kOIDCategory_Extension, static_cast(kTag_ExtendedKeyUsage)); + + ASN1_ENCODE_OBJECT_ID(extensionOID); + + // ExtKeyUsage extension MUST be marked as critical. + ASN1_ENCODE_BOOLEAN(true); + ASN1_START_OCTET_STRING_ENCAPSULATED + { + ASN1_START_SEQUENCE + { + ASN1_ENCODE_OBJECT_ID(kOID_KeyPurpose_ClientAuth); + ASN1_ENCODE_OBJECT_ID(kOID_KeyPurpose_ServerAuth); + } + ASN1_END_SEQUENCE; + } + ASN1_END_ENCAPSULATED; + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeExtensions(bool isCA, const Crypto::P256PublicKey & SKI, const Crypto::P256PublicKey & AKI, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 3) + { + ASN1_START_SEQUENCE + { + if (isCA) + { + ReturnErrorOnFailure(EncodeCASpecificExtensions(writer)); + } + else + { + ReturnErrorOnFailure(EncodeNOCSpecificExtensions(writer)); + } + + ReturnErrorOnFailure(EncodeSubjectKeyIdentifierExtension(SKI, writer)); + + ReturnErrorOnFailure(EncodeAuthorityKeyIdentifierExtension(AKI, writer)); + } + ASN1_END_SEQUENCE; + } + ASN1_END_CONSTRUCTED; + +exit: + return err; +} + +CHIP_ERROR EncodeChipDNs(ChipDNParams * params, uint8_t numParams, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_SEQUENCE + { + for (uint8_t i = 0; i < numParams; i++) + { + ASN1_START_SET + { + uint8_t chipAttrStr[kChip64bitAttrUTF8Length + 1]; + snprintf(reinterpret_cast(chipAttrStr), sizeof(chipAttrStr), "%016" PRIX64, params[i].Value); + + ASN1_START_SEQUENCE + { + ASN1_ENCODE_OBJECT_ID(params[i].AttrOID); + ReturnErrorOnFailure(writer.PutString(kASN1UniversalTag_UTF8String, Uint8::to_const_char(chipAttrStr), 16)); + } + ASN1_END_SEQUENCE; + } + ASN1_END_SET; + } + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeValidity(uint32_t validityStart, uint32_t validityEnd, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ASN1UniversalTime asn1Time; + + ASN1_START_SEQUENCE + { + ReturnErrorOnFailure(ChipEpochToASN1Time(validityStart, asn1Time)); + ASN1_ENCODE_TIME(asn1Time); + + ReturnErrorOnFailure(ChipEpochToASN1Time(validityEnd, asn1Time)); + ASN1_ENCODE_TIME(asn1Time); + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeChipECDSASignature(Crypto::P256ECDSASignature & signature, ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_BIT_STRING_ENCAPSULATED { writer.PutConstructedType(signature, (uint16_t) signature.Length()); } + ASN1_END_ENCAPSULATED; + +exit: + return err; +} + +} // namespace + +CHIP_ERROR EncodeTBSCert(const X509CertRequestParams & requestParams, CertificateIssuerLevel issuerLevel, uint64_t subject, + const Crypto::P256PublicKey & subjectPubkey, const Crypto::P256PublicKey & issuerPubkey, + ASN1Writer & writer) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipDNParams dnParams[2]; + uint8_t numDNs = 1; + bool isCA = true; + + VerifyOrReturnError(requestParams.SerialNumber > 0, CHIP_ERROR_INVALID_ARGUMENT); + + ASN1_START_SEQUENCE + { + // version [0] EXPLICIT Version DEFAULT v1 + ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) + { + // Version ::= INTEGER { v1(0), v2(1), v3(2) } + ASN1_ENCODE_INTEGER(2); + } + ASN1_END_CONSTRUCTED; + + ReturnErrorOnFailure(writer.PutInteger(requestParams.SerialNumber)); + + ASN1_START_SEQUENCE { ASN1_ENCODE_OBJECT_ID(kOID_SigAlgo_ECDSAWithSHA256); } + ASN1_END_SEQUENCE; + + // Issuer OID depends on if cert is being signed by the root CA + if (issuerLevel == kIssuerIsRootCA) + { + dnParams[0].AttrOID = chip::ASN1::kOID_AttributeType_ChipRootId; + } + else + { + dnParams[0].AttrOID = chip::ASN1::kOID_AttributeType_ChipICAId; + } + dnParams[0].Value = requestParams.Issuer; + + if (requestParams.HasFabricID) + { + dnParams[1].AttrOID = chip::ASN1::kOID_AttributeType_ChipFabricId; + dnParams[1].Value = requestParams.FabricID; + numDNs = 2; + } + ReturnErrorOnFailure(EncodeChipDNs(dnParams, numDNs, writer)); + + // validity Validity, + ReturnErrorOnFailure(EncodeValidity(requestParams.ValidityStart, requestParams.ValidityEnd, writer)); + + // subject Name + if (requestParams.HasNodeID) + { + dnParams[0].AttrOID = chip::ASN1::kOID_AttributeType_ChipNodeId; + + isCA = false; + } + else if (subjectPubkey != issuerPubkey) + { + dnParams[0].AttrOID = chip::ASN1::kOID_AttributeType_ChipICAId; + } + dnParams[0].Value = subject; + + ReturnErrorOnFailure(EncodeChipDNs(dnParams, numDNs, writer)); + + ReturnErrorOnFailure(EncodeSubjectPublicKeyInfo(subjectPubkey, writer)); + + // certificate extensions + ReturnErrorOnFailure(EncodeExtensions(isCA, subjectPubkey, issuerPubkey, writer)); + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR NewChipX509Cert(const X509CertRequestParams & requestParams, CertificateIssuerLevel issuerLevel, uint64_t subject, + const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair, uint8_t * x509CertBuf, + uint32_t x509CertBufSize, uint32_t & x509CertLen) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ASN1Writer writer; + writer.Init(x509CertBuf, x509CertBufSize); + + ReturnErrorOnFailure(EncodeTBSCert(requestParams, issuerLevel, subject, subjectPubkey, issuerKeypair.Pubkey(), writer)); + writer.Finalize(); + + Crypto::P256ECDSASignature signature; + ReturnErrorOnFailure(issuerKeypair.ECDSA_sign_msg(x509CertBuf, writer.GetLengthWritten(), signature)); + + writer.Init(x509CertBuf, x509CertBufSize); + + ASN1_START_SEQUENCE + { + ReturnErrorOnFailure(EncodeTBSCert(requestParams, issuerLevel, subject, subjectPubkey, issuerKeypair.Pubkey(), writer)); + + ASN1_START_SEQUENCE { ASN1_ENCODE_OBJECT_ID(kOID_SigAlgo_ECDSAWithSHA256); } + ASN1_END_SEQUENCE; + + ReturnErrorOnFailure(EncodeChipECDSASignature(signature, writer)); + } + ASN1_END_SEQUENCE; + + writer.Finalize(); + x509CertLen = writer.GetLengthWritten(); + +exit: + return err; +} + +DLL_EXPORT CHIP_ERROR NewRootX509Cert(const X509CertRequestParams & requestParams, Crypto::P256Keypair & issuerKeypair, + uint8_t * x509CertBuf, uint32_t x509CertBufSize, uint32_t & x509CertLen) +{ + ReturnErrorCodeIf(requestParams.HasNodeID, CHIP_ERROR_INVALID_ARGUMENT); + return NewChipX509Cert(requestParams, kIssuerIsRootCA, requestParams.Issuer, issuerKeypair.Pubkey(), issuerKeypair, x509CertBuf, + x509CertBufSize, x509CertLen); +} + +DLL_EXPORT CHIP_ERROR NewICAX509Cert(const X509CertRequestParams & requestParams, uint64_t subject, + const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair, + uint8_t * x509CertBuf, uint32_t x509CertBufSize, uint32_t & x509CertLen) +{ + ReturnErrorCodeIf(requestParams.HasNodeID, CHIP_ERROR_INVALID_ARGUMENT); + return NewChipX509Cert(requestParams, kIssuerIsRootCA, subject, subjectPubkey, issuerKeypair, x509CertBuf, x509CertBufSize, + x509CertLen); +} + +DLL_EXPORT CHIP_ERROR NewNodeOperationalX509Cert(const X509CertRequestParams & requestParams, CertificateIssuerLevel issuerLevel, + const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair, + uint8_t * x509CertBuf, uint32_t x509CertBufSize, uint32_t & x509CertLen) +{ + VerifyOrReturnError(requestParams.HasNodeID, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(requestParams.HasFabricID, CHIP_ERROR_INVALID_ARGUMENT); + return NewChipX509Cert(requestParams, issuerLevel, requestParams.NodeID, subjectPubkey, issuerKeypair, x509CertBuf, + x509CertBufSize, x509CertLen); +} + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/tests/TestChipCert.cpp b/src/credentials/tests/TestChipCert.cpp index 2adc764d209c71..4d561ad6704e86 100644 --- a/src/credentials/tests/TestChipCert.cpp +++ b/src/credentials/tests/TestChipCert.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,7 @@ using namespace chip::ASN1; using namespace chip::TLV; using namespace chip::Credentials; using namespace chip::TestCerts; +using namespace chip::Crypto; enum { @@ -635,6 +637,263 @@ static void TestChipCert_CertType(nlTestSuite * inSuite, void * inContext) } } +static void TestChipCert_GenerateRootCert(nlTestSuite * inSuite, void * inContext) +{ + // Generate a new keypair for cert signing + P256Keypair keypair; + NL_TEST_ASSERT(inSuite, keypair.Initialize() == CHIP_NO_ERROR); + + uint8_t signed_cert[kTestCertBufSize]; + uint32_t signed_len = 0; + + ChipCertificateData certData; + + X509CertRequestParams root_params = { 1234, 0xabcdabcd, 9876, 98790000, false, 0, false, 0 }; + + NL_TEST_ASSERT(inSuite, NewRootX509Cert(root_params, keypair, signed_cert, sizeof(signed_cert), signed_len) == CHIP_NO_ERROR); + + uint8_t outCertBuf[kTestCertBufSize]; + uint32_t outCertLen; + + NL_TEST_ASSERT(inSuite, + ConvertX509CertToChipCert(signed_cert, signed_len, outCertBuf, sizeof(outCertBuf), outCertLen) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, DecodeChipCert(outCertBuf, outCertLen, certData) == CHIP_NO_ERROR); + + // Test that root cert cannot be provided a node ID + root_params.HasNodeID = true; + NL_TEST_ASSERT(inSuite, + NewRootX509Cert(root_params, keypair, signed_cert, sizeof(signed_cert), signed_len) == + CHIP_ERROR_INVALID_ARGUMENT); + + // Test that serial number cannot be negative + root_params.HasNodeID = false; + root_params.SerialNumber = -1; + NL_TEST_ASSERT(inSuite, + NewRootX509Cert(root_params, keypair, signed_cert, sizeof(signed_cert), signed_len) == + CHIP_ERROR_INVALID_ARGUMENT); +} + +static void TestChipCert_GenerateRootFabCert(nlTestSuite * inSuite, void * inContext) +{ + // Generate a new keypair for cert signing + P256Keypair keypair; + NL_TEST_ASSERT(inSuite, keypair.Initialize() == CHIP_NO_ERROR); + + uint8_t signed_cert[kTestCertBufSize]; + uint32_t signed_len = 0; + + ChipCertificateData certData; + + uint8_t outCertBuf[kTestCertBufSize]; + uint32_t outCertLen; + + X509CertRequestParams root_params_fabric = { 1234, 0xabcdabcd, 9876, 98790000, true, 0xabcd, false, 0 }; + + NL_TEST_ASSERT(inSuite, + NewRootX509Cert(root_params_fabric, keypair, signed_cert, sizeof(signed_cert), signed_len) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + ConvertX509CertToChipCert(signed_cert, signed_len, outCertBuf, sizeof(outCertBuf), outCertLen) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, DecodeChipCert(outCertBuf, outCertLen, certData) == CHIP_NO_ERROR); +} + +static void TestChipCert_GenerateICACert(nlTestSuite * inSuite, void * inContext) +{ + // Generate a new keypair for cert signing + P256Keypair keypair; + NL_TEST_ASSERT(inSuite, keypair.Initialize() == CHIP_NO_ERROR); + + uint8_t signed_cert[kTestCertBufSize]; + uint32_t signed_len = 0; + + uint8_t outCertBuf[kTestCertBufSize]; + uint32_t outCertLen; + + ChipCertificateData certData; + + X509CertRequestParams ica_params = { 1234, 0xabcdabcd, 9876, 98790000, false, 0, false, 0 }; + P256Keypair ica_keypair; + NL_TEST_ASSERT(inSuite, ica_keypair.Initialize() == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + NewICAX509Cert(ica_params, 4321, ica_keypair.Pubkey(), keypair, signed_cert, sizeof(signed_cert), signed_len) == + CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + ConvertX509CertToChipCert(signed_cert, signed_len, outCertBuf, sizeof(outCertBuf), outCertLen) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, DecodeChipCert(outCertBuf, outCertLen, certData) == CHIP_NO_ERROR); + + // Test that ICA cert cannot be provided a node ID + ica_params.HasNodeID = true; + NL_TEST_ASSERT(inSuite, + NewICAX509Cert(ica_params, 4321, ica_keypair.Pubkey(), keypair, signed_cert, sizeof(signed_cert), signed_len) == + CHIP_ERROR_INVALID_ARGUMENT); + + // Test that serial number cannot be negative + ica_params.HasNodeID = false; + ica_params.SerialNumber = -1; + NL_TEST_ASSERT(inSuite, + NewICAX509Cert(ica_params, 4321, ica_keypair.Pubkey(), keypair, signed_cert, sizeof(signed_cert), signed_len) == + CHIP_ERROR_INVALID_ARGUMENT); +} + +static void TestChipCert_GenerateNOCRoot(nlTestSuite * inSuite, void * inContext) +{ + // Generate a new keypair for cert signing + P256Keypair keypair; + NL_TEST_ASSERT(inSuite, keypair.Initialize() == CHIP_NO_ERROR); + + uint8_t signed_cert[kTestCertBufSize]; + uint32_t signed_len = 0; + + uint8_t outCertBuf[kTestCertBufSize]; + uint32_t outCertLen; + + ChipCertificateData certData; + + X509CertRequestParams noc_params = { 1234, 0xabcdabcd, 9876, 98790000, true, 0x8888, true, 0x1234 }; + P256Keypair noc_keypair; + NL_TEST_ASSERT(inSuite, noc_keypair.Initialize() == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + NewNodeOperationalX509Cert(noc_params, kIssuerIsRootCA, noc_keypair.Pubkey(), keypair, signed_cert, + sizeof(signed_cert), signed_len) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + ConvertX509CertToChipCert(signed_cert, signed_len, outCertBuf, sizeof(outCertBuf), outCertLen) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, DecodeChipCert(outCertBuf, outCertLen, certData) == CHIP_NO_ERROR); + + // Test that NOC cert must be provided a node ID + noc_params.HasNodeID = false; + NL_TEST_ASSERT(inSuite, + NewNodeOperationalX509Cert(noc_params, kIssuerIsRootCA, noc_keypair.Pubkey(), keypair, signed_cert, + sizeof(signed_cert), signed_len) == CHIP_ERROR_INVALID_ARGUMENT); + + // Test that NOC cert must be provided a fabric ID + noc_params.HasNodeID = true; + noc_params.HasFabricID = false; + NL_TEST_ASSERT(inSuite, + NewNodeOperationalX509Cert(noc_params, kIssuerIsRootCA, noc_keypair.Pubkey(), keypair, signed_cert, + sizeof(signed_cert), signed_len) == CHIP_ERROR_INVALID_ARGUMENT); + + // Test that serial number cannot be negative + noc_params.HasNodeID = true; + noc_params.HasFabricID = true; + noc_params.SerialNumber = -1; + NL_TEST_ASSERT(inSuite, + NewNodeOperationalX509Cert(noc_params, kIssuerIsRootCA, noc_keypair.Pubkey(), keypair, signed_cert, + sizeof(signed_cert), signed_len) == CHIP_ERROR_INVALID_ARGUMENT); +} + +static void TestChipCert_GenerateNOCICA(nlTestSuite * inSuite, void * inContext) +{ + // Generate a new keypair for cert signing + P256Keypair keypair; + NL_TEST_ASSERT(inSuite, keypair.Initialize() == CHIP_NO_ERROR); + + uint8_t signed_cert[kTestCertBufSize]; + uint32_t signed_len = 0; + + uint8_t outCertBuf[kTestCertBufSize]; + uint32_t outCertLen; + + ChipCertificateData certData; + + X509CertRequestParams noc_params = { 1234, 0xabcdabcd, 9876, 98790000, true, 0x8888, true, 0x1234 }; + P256Keypair noc_keypair; + NL_TEST_ASSERT(inSuite, noc_keypair.Initialize() == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + NewNodeOperationalX509Cert(noc_params, kIssuerIsIntermediateCA, noc_keypair.Pubkey(), keypair, signed_cert, + sizeof(signed_cert), signed_len) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + ConvertX509CertToChipCert(signed_cert, signed_len, outCertBuf, sizeof(outCertBuf), outCertLen) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, DecodeChipCert(outCertBuf, outCertLen, certData) == CHIP_NO_ERROR); +} + +static void TestChipCert_VerifyGeneratedCerts(nlTestSuite * inSuite, void * inContext) +{ + // Generate a new keypair for cert signing + P256Keypair keypair; + NL_TEST_ASSERT(inSuite, keypair.Initialize() == CHIP_NO_ERROR); + + static uint8_t root_cert[kTestCertBufSize]; + uint32_t root_len = 0; + + X509CertRequestParams root_params = { 1234, 0xabcdabcd, 9876, 98790000, true, 0x8888, false, 0 }; + NL_TEST_ASSERT(inSuite, NewRootX509Cert(root_params, keypair, root_cert, sizeof(root_cert), root_len) == CHIP_NO_ERROR); + + static uint8_t ica_cert[kTestCertBufSize]; + uint32_t ica_len = 0; + + X509CertRequestParams ica_params = { 1234, 0xabcdabcd, 9876, 98790000, true, 0x8888, false, 0 }; + P256Keypair ica_keypair; + NL_TEST_ASSERT(inSuite, ica_keypair.Initialize() == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + NewICAX509Cert(ica_params, 0xaabbccdd, ica_keypair.Pubkey(), keypair, ica_cert, sizeof(ica_cert), ica_len) == + CHIP_NO_ERROR); + + static uint8_t noc_cert[kTestCertBufSize]; + uint32_t noc_len = 0; + + X509CertRequestParams noc_params = { 1234, 0xaabbccdd, 9876, 98790000, true, 0x8888, true, 0x1234 }; + P256Keypair noc_keypair; + NL_TEST_ASSERT(inSuite, noc_keypair.Initialize() == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + NewNodeOperationalX509Cert(noc_params, kIssuerIsIntermediateCA, noc_keypair.Pubkey(), ica_keypair, noc_cert, + sizeof(noc_cert), noc_len) == CHIP_NO_ERROR); + + ChipCertificateSet certSet; + NL_TEST_ASSERT(inSuite, certSet.Init(3, kTestCertBufSize * 3) == CHIP_NO_ERROR); + + static uint8_t rootCertBuf[kTestCertBufSize]; + static uint8_t icaCertBuf[kTestCertBufSize]; + static uint8_t nocCertBuf[kTestCertBufSize]; + uint32_t outCertLen; + + NL_TEST_ASSERT(inSuite, + ConvertX509CertToChipCert(root_cert, root_len, rootCertBuf, sizeof(rootCertBuf), outCertLen) == CHIP_NO_ERROR); + NL_TEST_ASSERT( + inSuite, + certSet.LoadCert(rootCertBuf, outCertLen, + BitFlags(CertDecodeFlags::kIsTrustAnchor).Set(CertDecodeFlags::kGenerateTBSHash)) == + CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + ConvertX509CertToChipCert(ica_cert, ica_len, icaCertBuf, sizeof(icaCertBuf), outCertLen) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + certSet.LoadCert(icaCertBuf, outCertLen, BitFlags(CertDecodeFlags::kGenerateTBSHash)) == + CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + ConvertX509CertToChipCert(noc_cert, noc_len, nocCertBuf, sizeof(nocCertBuf), outCertLen) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + certSet.LoadCert(nocCertBuf, outCertLen, BitFlags(CertDecodeFlags::kGenerateTBSHash)) == + CHIP_NO_ERROR); + + ValidationContext validContext; + + validContext.Reset(); + NL_TEST_ASSERT(inSuite, SetEffectiveTime(validContext, 2022, 1, 1) == CHIP_NO_ERROR); + validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); + validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); + + // Locate the subject DN and key id that will be used as input the FindValidCert() method. + const ChipDN & subjectDN = certSet.GetCertSet()[2].mSubjectDN; + const CertificateKeyId & subjectKeyId = certSet.GetCertSet()[2].mSubjectKeyId; + + ChipCertificateData * resultCert = nullptr; + NL_TEST_ASSERT(inSuite, certSet.FindValidCert(subjectDN, subjectKeyId, validContext, resultCert) == CHIP_NO_ERROR); +} + /** * Set up the test suite. */ @@ -670,6 +929,12 @@ static const nlTest sTests[] = { NL_TEST_DEF("Test CHIP Certificate Validation time", TestChipCert_CertValidTime), NL_TEST_DEF("Test CHIP Certificate Usage", TestChipCert_CertUsage), NL_TEST_DEF("Test CHIP Certificate Type", TestChipCert_CertType), + NL_TEST_DEF("Test CHIP Generate Root Certificate", TestChipCert_GenerateRootCert), + NL_TEST_DEF("Test CHIP Generate Root Certificate with Fabric", TestChipCert_GenerateRootFabCert), + NL_TEST_DEF("Test CHIP Generate ICA Certificate", TestChipCert_GenerateICACert), + NL_TEST_DEF("Test CHIP Generate NOC using Root", TestChipCert_GenerateNOCRoot), + NL_TEST_DEF("Test CHIP Generate NOC using ICA", TestChipCert_GenerateNOCICA), + NL_TEST_DEF("Test CHIP Verify Generated Cert Chain", TestChipCert_VerifyGeneratedCerts), NL_TEST_SENTINEL() }; // clang-format on diff --git a/src/crypto/CHIPCryptoPAL.h b/src/crypto/CHIPCryptoPAL.h index 9174a084a24636..f144e52984aaef 100644 --- a/src/crypto/CHIPCryptoPAL.h +++ b/src/crypto/CHIPCryptoPAL.h @@ -224,7 +224,7 @@ class ECPKeypair **/ virtual CHIP_ERROR ECDH_derive_secret(const PK & remote_public_key, Secret & out_secret) const = 0; - virtual const PK & Pubkey() = 0; + virtual const PK & Pubkey() const = 0; }; struct alignas(size_t) P256KeypairContext @@ -240,22 +240,26 @@ class P256Keypair : public ECPKeypair #include #include +#include #include #include @@ -189,6 +190,19 @@ CHIP_ERROR Hash_SHA256(const uint8_t * data, const size_t data_length, uint8_t * return error; } +CHIP_ERROR Hash_SHA1(const uint8_t * data, const size_t data_length, uint8_t * out_buffer) +{ + int result = 0; + + // zero data length hash is supported. + VerifyOrReturnError(out_buffer != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + result = mbedtls_sha1_ret(Uint8::to_const_uchar(data), data_length, Uint8::to_uchar(out_buffer)); + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + Hash_SHA256_stream::Hash_SHA256_stream(void) {} Hash_SHA256_stream::~Hash_SHA256_stream(void) {}