diff --git a/src/credentials/BUILD.gn b/src/credentials/BUILD.gn index e9f8b1f0a73360..a62069b3ed55c9 100644 --- a/src/credentials/BUILD.gn +++ b/src/credentials/BUILD.gn @@ -23,6 +23,8 @@ static_library("credentials") { "CHIPCert.h", "CHIPCertFromX509.cpp", "CHIPCertToX509.cpp", + "CertificationDeclaration.cpp", + "CertificationDeclaration.h", "DeviceAttestationConstructor.cpp", "DeviceAttestationConstructor.h", "DeviceAttestationCredsProvider.cpp", diff --git a/src/credentials/CertificationDeclaration.cpp b/src/credentials/CertificationDeclaration.cpp new file mode 100644 index 00000000000000..7c3bef2b349480 --- /dev/null +++ b/src/credentials/CertificationDeclaration.cpp @@ -0,0 +1,564 @@ +/* + * + * 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 data types, objects and APIs for + * working with Certification Declaration elements. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace Credentials { + +using namespace chip::ASN1; +using namespace chip::TLV; +using namespace chip::Crypto; + +static constexpr uint8_t sOID_ContentType_PKCS7Data[] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 }; +static constexpr uint8_t sOID_ContentType_PKCS7SignedData[] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02 }; +static constexpr uint8_t sOID_DigestAlgo_SHA256[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 }; +static constexpr uint8_t sOID_SigAlgo_ECDSAWithSHA256[] = { 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }; + +/** Certification Declaration Element TLV Tags + */ +enum +{ + kTag_VendorId = 1, /**< [ unsigned int ] Vedor identifier. */ + kTag_ProductIds = 2, /**< [ array ] Product identifiers (each is unsigned int). */ + kTag_ServerCategoryId = 3, /**< [ unsigned int ] Server category identifier. */ + kTag_ClientCategoryId = 4, /**< [ unsigned int ] Client category identifier. */ + kTag_SecurityLevel = 5, /**< [ unsigned int ] Security level. */ + kTag_SecurityInformation = 6, /**< [ unsigned int ] Security information. */ + kTag_VersionNumber = 7, /**< [ unsigned int ] Version number. */ + kTag_CertificationType = 8, /**< [ unsigned int ] Certification Type. */ +}; + +CHIP_ERROR EncodeCertificationElements(const CertificationElements & certElements, MutableByteSpan & encodedCertElements) +{ + TLVWriter writer; + TLVType outerContainer1, outerContainer2; + + writer.Init(encodedCertElements); + + ReturnErrorOnFailure(writer.StartContainer(AnonymousTag, kTLVType_Structure, outerContainer1)); + + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_VendorId), certElements.VendorId)); + + VerifyOrReturnError(certElements.ProductIdsCount > 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(certElements.ProductIdsCount < kMaxProductIdsCountPerCD, CHIP_ERROR_INVALID_ARGUMENT); + + ReturnErrorOnFailure(writer.StartContainer(ContextTag(kTag_ProductIds), kTLVType_Array, outerContainer2)); + for (uint8_t i = 0; i < certElements.ProductIdsCount; i++) + { + ReturnErrorOnFailure(writer.Put(AnonymousTag, certElements.ProductIds[i])); + } + ReturnErrorOnFailure(writer.EndContainer(outerContainer2)); + + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_ServerCategoryId), certElements.ServerCategoryId)); + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_ClientCategoryId), certElements.ClientCategoryId)); + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_SecurityLevel), certElements.SecurityLevel)); + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_SecurityInformation), certElements.SecurityInformation)); + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_VersionNumber), certElements.VersionNumber)); + ReturnErrorOnFailure(writer.Put(ContextTag(kTag_CertificationType), certElements.CertificationType)); + + ReturnErrorOnFailure(writer.EndContainer(outerContainer1)); + + ReturnErrorOnFailure(writer.Finalize()); + + encodedCertElements.reduce_size(writer.GetLengthWritten()); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, CertificationElements & certElements) +{ + TLVReader reader; + TLVType outerContainer1, outerContainer2; + uint64_t element; + + reader.Init(encodedCertElements); + + ReturnErrorOnFailure(reader.Next(kTLVType_Structure, AnonymousTag)); + + ReturnErrorOnFailure(reader.EnterContainer(outerContainer1)); + + ReturnErrorOnFailure(reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_VendorId))); + ReturnErrorOnFailure(reader.Get(element)); + VerifyOrReturnError(CanCastTo(element), CHIP_ERROR_INVALID_TLV_ELEMENT); + certElements.VendorId = static_cast(element); + + ReturnErrorOnFailure(reader.Next(kTLVType_Array, ContextTag(kTag_ProductIds))); + ReturnErrorOnFailure(reader.EnterContainer(outerContainer2)); + + certElements.ProductIdsCount = 0; + while (reader.Next(kTLVType_UnsignedInteger, AnonymousTag) == CHIP_NO_ERROR) + { + ReturnErrorOnFailure(reader.Get(element)); + VerifyOrReturnError(CanCastTo(element), CHIP_ERROR_INVALID_TLV_ELEMENT); + certElements.ProductIds[certElements.ProductIdsCount++] = static_cast(element); + } + ReturnErrorOnFailure(reader.VerifyEndOfContainer()); + ReturnErrorOnFailure(reader.ExitContainer(outerContainer2)); + + ReturnErrorOnFailure(reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_ServerCategoryId))); + ReturnErrorOnFailure(reader.Get(element)); + VerifyOrReturnError(CanCastTo(element), CHIP_ERROR_INVALID_TLV_ELEMENT); + certElements.ServerCategoryId = static_cast(element); + + ReturnErrorOnFailure(reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_ClientCategoryId))); + ReturnErrorOnFailure(reader.Get(element)); + VerifyOrReturnError(CanCastTo(element), CHIP_ERROR_INVALID_TLV_ELEMENT); + certElements.ClientCategoryId = static_cast(element); + + ReturnErrorOnFailure(reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_SecurityLevel))); + ReturnErrorOnFailure(reader.Get(element)); + VerifyOrReturnError(CanCastTo(element), CHIP_ERROR_INVALID_TLV_ELEMENT); + certElements.SecurityLevel = static_cast(element); + + ReturnErrorOnFailure(reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_SecurityInformation))); + ReturnErrorOnFailure(reader.Get(element)); + VerifyOrReturnError(CanCastTo(element), CHIP_ERROR_INVALID_TLV_ELEMENT); + certElements.SecurityInformation = static_cast(element); + + ReturnErrorOnFailure(reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_VersionNumber))); + ReturnErrorOnFailure(reader.Get(element)); + VerifyOrReturnError(CanCastTo(element), CHIP_ERROR_INVALID_TLV_ELEMENT); + certElements.VersionNumber = static_cast(element); + + ReturnErrorOnFailure(reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_CertificationType))); + ReturnErrorOnFailure(reader.Get(element)); + VerifyOrReturnError(CanCastTo(element), CHIP_ERROR_INVALID_TLV_ELEMENT); + certElements.CertificationType = static_cast(element); + + ReturnErrorOnFailure(reader.VerifyEndOfContainer()); + + ReturnErrorOnFailure(reader.ExitContainer(outerContainer1)); + + ReturnErrorOnFailure(reader.VerifyEndOfContainer()); + + return CHIP_NO_ERROR; +} + +namespace { + +CHIP_ERROR EncodeEncapsulatedContent(const ByteSpan & cdContent, ASN1Writer & writer) +{ + /** + * EncapsulatedContentInfo ::= SEQUENCE { + * eContentType OBJECT IDENTIFIER pkcs7-data (1.2.840.113549.1.7.1), + * eContent [0] EXPLICIT OCTET STRING cd_content } + */ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_SEQUENCE + { + // eContentType OBJECT IDENTIFIER pkcs7-data (1.2.840.113549.1.7.1) + ReturnErrorOnFailure(writer.PutObjectId(sOID_ContentType_PKCS7Data, sizeof(sOID_ContentType_PKCS7Data))); + + // eContent [0] EXPLICIT OCTET STRING cd_content + ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) + { + // OCTET STRING cd_content + ReturnErrorOnFailure(writer.PutOctetString(cdContent.data(), static_cast(cdContent.size()))); + } + ASN1_END_CONSTRUCTED; + } + ASN1_END_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR DecodeEncapsulatedContent(ASN1Reader & reader, ByteSpan & cdContent) +{ + /** + * EncapsulatedContentInfo ::= SEQUENCE { + * eContentType OBJECT IDENTIFIER pkcs7-data (1.2.840.113549.1.7.1), + * eContent [0] EXPLICIT OCTET STRING cd_content } + */ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_PARSE_ENTER_SEQUENCE + { + // eContentType OBJECT IDENTIFIER pkcs7-data (1.2.840.113549.1.7.1) + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_ObjectId); + VerifyOrReturnError(ByteSpan(reader.GetValue(), reader.GetValueLen()).data_equal(ByteSpan(sOID_ContentType_PKCS7Data)), + ASN1_ERROR_UNSUPPORTED_ENCODING); + + // eContent [0] EXPLICIT OCTET STRING cd_content + ASN1_PARSE_ENTER_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) + { + // OCTET STRING cd_content + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_OctetString); + cdContent = ByteSpan(reader.GetValue(), reader.GetValueLen()); + } + ASN1_EXIT_CONSTRUCTED; + } + ASN1_EXIT_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR EncodeSignerInfo(const ByteSpan & signerKeyId, const P256ECDSASignature & signature, ASN1Writer & writer) +{ + /** + * SignerInfo ::= SEQUENCE { + * version INTEGER ( v3(3) ), + * subjectKeyIdentifier OCTET STRING, + * digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1), + * signatureAlgorithm OBJECT IDENTIFIER ecdsa-with-SHA256 (1.2.840.10045.4.3.2), + * signature OCTET STRING } + */ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_START_SET + { + ASN1_START_SEQUENCE + { + // version INTEGER ( v3(3) ) + ASN1_ENCODE_INTEGER(3); + + // subjectKeyIdentifier OCTET STRING + ReturnErrorOnFailure(writer.PutOctetString(kASN1TagClass_ContextSpecific, 0, signerKeyId.data(), + static_cast(signerKeyId.size()))); + + // digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1) + ASN1_START_SEQUENCE + { + ReturnErrorOnFailure(writer.PutObjectId(sOID_DigestAlgo_SHA256, sizeof(sOID_DigestAlgo_SHA256))); + } + ASN1_END_SEQUENCE; + + // signatureAlgorithm OBJECT IDENTIFIER ecdsa-with-SHA256 (1.2.840.10045.4.3.2) + ASN1_START_SEQUENCE { ASN1_ENCODE_OBJECT_ID(kOID_SigAlgo_ECDSAWithSHA256); } + ASN1_END_SEQUENCE; + + uint8_t asn1SignatureBuf[kMax_ECDSA_Signature_Length_Der]; + MutableByteSpan asn1Signature(asn1SignatureBuf); + ReturnErrorOnFailure(EcdsaRawSignatureToAsn1(kP256_FE_Length, ByteSpan(signature, signature.Length()), asn1Signature)); + + // signature OCTET STRING + ReturnErrorOnFailure(writer.PutOctetString(asn1Signature.data(), static_cast(asn1Signature.size()))); + } + ASN1_END_SEQUENCE; + } + ASN1_END_SET; + +exit: + return err; +} + +CHIP_ERROR DecodeSignerInfo(ASN1Reader & reader, ByteSpan & signerKeyId, P256ECDSASignature & signature) +{ + /** + * SignerInfo ::= SEQUENCE { + * version INTEGER ( v3(3) ), + * subjectKeyIdentifier OCTET STRING, + * digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1), + * signatureAlgorithm OBJECT IDENTIFIER ecdsa-with-SHA256 (1.2.840.10045.4.3.2), + * signature OCTET STRING } + */ + CHIP_ERROR err = CHIP_NO_ERROR; + + ASN1_PARSE_ENTER_SET + { + ASN1_PARSE_ENTER_SEQUENCE + { + // version INTEGER ( v3(3) ) + { + int64_t version; + ASN1_PARSE_INTEGER(version); + + // Verify that the CMS version is v3 + VerifyOrExit(version == 3, err = ASN1_ERROR_UNSUPPORTED_ENCODING); + } + + // subjectKeyIdentifier OCTET STRING + ASN1_PARSE_ELEMENT(kASN1TagClass_ContextSpecific, 0); + signerKeyId = ByteSpan(reader.GetValue(), reader.GetValueLen()); + + // digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1) + ASN1_PARSE_ENTER_SEQUENCE + { + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_ObjectId); + VerifyOrReturnError(ByteSpan(reader.GetValue(), reader.GetValueLen()).data_equal(ByteSpan(sOID_DigestAlgo_SHA256)), + ASN1_ERROR_UNSUPPORTED_ENCODING); + } + ASN1_EXIT_SEQUENCE; + + // signatureAlgorithm OBJECT IDENTIFIER ecdsa-with-SHA256 (1.2.840.10045.4.3.2) + ASN1_PARSE_ENTER_SEQUENCE + { + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_ObjectId); + VerifyOrReturnError( + ByteSpan(reader.GetValue(), reader.GetValueLen()).data_equal(ByteSpan(sOID_SigAlgo_ECDSAWithSHA256)), + ASN1_ERROR_UNSUPPORTED_ENCODING); + } + ASN1_EXIT_SEQUENCE; + + // signature OCTET STRING + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_OctetString); + + MutableByteSpan signatureSpan(signature, signature.Capacity()); + ReturnErrorOnFailure( + EcdsaAsn1SignatureToRaw(kP256_FE_Length, ByteSpan(reader.GetValue(), reader.GetValueLen()), signatureSpan)); + ReturnErrorOnFailure(signature.SetLength(signatureSpan.size())); + } + ASN1_EXIT_SEQUENCE; + } + ASN1_EXIT_SET; + +exit: + return err; +} + +} // namespace + +CHIP_ERROR CMS_Sign(const ByteSpan & cdContent, const ByteSpan & signerKeyId, Crypto::P256Keypair & signerKeypair, + MutableByteSpan & signedMessage) +{ + /** + * CertificationDeclaration ::= SEQUENCE { + * version INTEGER ( v3(3) ), + * digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1), + * encapContentInfo EncapsulatedContentInfo, + * signerInfo SignerInfo } + */ + CHIP_ERROR err = CHIP_NO_ERROR; + ASN1Writer writer; + uint32_t size = static_cast(std::min(static_cast(UINT32_MAX), signedMessage.size())); + + writer.Init(signedMessage.data(), size); + + ASN1_START_SEQUENCE + { + // OID identifies the CMS signed-data content type + ReturnErrorOnFailure(writer.PutObjectId(sOID_ContentType_PKCS7SignedData, sizeof(sOID_ContentType_PKCS7SignedData))); + + ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) + { + ASN1_START_SEQUENCE + { + // version INTEGER ( v3(3) ) + ASN1_ENCODE_INTEGER(3); + + // digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1) + ASN1_START_SET + { + ASN1_START_SEQUENCE + { + ReturnErrorOnFailure(writer.PutObjectId(sOID_DigestAlgo_SHA256, sizeof(sOID_DigestAlgo_SHA256))); + } + ASN1_END_SEQUENCE; + } + ASN1_END_SET; + + // encapContentInfo EncapsulatedContentInfo + ReturnErrorOnFailure(EncodeEncapsulatedContent(cdContent, writer)); + + Crypto::P256ECDSASignature signature; + ReturnErrorOnFailure(signerKeypair.ECDSA_sign_msg(cdContent.data(), cdContent.size(), signature)); + + // signerInfo SignerInfo + ReturnErrorOnFailure(EncodeSignerInfo(signerKeyId, signature, writer)); + } + ASN1_END_SEQUENCE; + } + ASN1_END_CONSTRUCTED; + } + ASN1_END_SEQUENCE; + + signedMessage.reduce_size(writer.GetLengthWritten()); + +exit: + return err; +} + +CHIP_ERROR CMS_Verify(const ByteSpan & signedMessage, const ByteSpan & signerX509Cert, ByteSpan & cdContent) +{ + P256PublicKey signerPubkey; + + ReturnErrorOnFailure(ExtractPubkeyFromX509Cert(signerX509Cert, signerPubkey)); + + return CMS_Verify(signedMessage, signerPubkey, cdContent); +} + +CHIP_ERROR CMS_Verify(const ByteSpan & signedMessage, const Crypto::P256PublicKey & signerPubkey, ByteSpan & cdContent) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ASN1Reader reader; + uint32_t size = signedMessage.size() > UINT32_MAX ? UINT32_MAX : static_cast(signedMessage.size()); + + reader.Init(signedMessage.data(), size); + + // SignedData ::= SEQUENCE + ASN1_PARSE_ENTER_SEQUENCE + { + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_ObjectId); + + // id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 } + // OID identifies the CMS signed-data content type + VerifyOrReturnError( + ByteSpan(reader.GetValue(), reader.GetValueLen()).data_equal(ByteSpan(sOID_ContentType_PKCS7SignedData)), + ASN1_ERROR_UNSUPPORTED_ENCODING); + + // version [0] EXPLICIT Version DEFAULT v3 + ASN1_PARSE_ENTER_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) + { + ASN1_PARSE_ENTER_SEQUENCE + { + // Version ::= INTEGER { v3(3) } + int64_t version; + ASN1_PARSE_INTEGER(version); + + // Verify that the CMS version is v3 + VerifyOrExit(version == 3, err = ASN1_ERROR_UNSUPPORTED_ENCODING); + + // digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1) + ASN1_PARSE_ENTER_SET + { + ASN1_PARSE_ENTER_SEQUENCE + { + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_ObjectId); + VerifyOrReturnError( + ByteSpan(reader.GetValue(), reader.GetValueLen()).data_equal(ByteSpan(sOID_DigestAlgo_SHA256)), + ASN1_ERROR_UNSUPPORTED_ENCODING); + } + ASN1_EXIT_SEQUENCE; + } + ASN1_EXIT_SET; + + // encapContentInfo EncapsulatedContentInfo + ReturnErrorOnFailure(DecodeEncapsulatedContent(reader, cdContent)); + + // signerInfo SignerInfo + ByteSpan signerKeyId; + P256ECDSASignature signature; + ReturnErrorOnFailure(DecodeSignerInfo(reader, signerKeyId, signature)); + + // Validate CD Signature + ReturnErrorOnFailure(signerPubkey.ECDSA_validate_msg_signature(cdContent.data(), cdContent.size(), signature)); + } + ASN1_EXIT_SEQUENCE; + } + ASN1_EXIT_CONSTRUCTED; + } + ASN1_EXIT_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR CMS_ExtractKeyId(const ByteSpan & signedMessage, ByteSpan & signerKeyId) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ASN1Reader reader; + uint32_t size = signedMessage.size() > UINT32_MAX ? UINT32_MAX : static_cast(signedMessage.size()); + + reader.Init(signedMessage.data(), size); + + // SignedData ::= SEQUENCE + ASN1_PARSE_ENTER_SEQUENCE + { + // id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 } + // OID identifies the CMS signed-data content type + ASN1_PARSE_ANY; + + // version [0] EXPLICIT Version DEFAULT v3 + ASN1_PARSE_ENTER_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) + { + ASN1_PARSE_ENTER_SEQUENCE + { + // Version ::= INTEGER { v3(3) } + ASN1_PARSE_ANY; + + // digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1) + ASN1_PARSE_ANY; + + // encapContentInfo EncapsulatedContentInfo + ASN1_PARSE_ANY; + + // signerInfo SignerInfo + P256ECDSASignature signature; + ReturnErrorOnFailure(DecodeSignerInfo(reader, signerKeyId, signature)); + } + ASN1_EXIT_SEQUENCE; + } + ASN1_EXIT_CONSTRUCTED; + } + ASN1_EXIT_SEQUENCE; + +exit: + return err; +} + +CHIP_ERROR CMS_ExtractCDContent(const ByteSpan & signedMessage, ByteSpan & cdContent) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ASN1Reader reader; + uint32_t size = signedMessage.size() > UINT32_MAX ? UINT32_MAX : static_cast(signedMessage.size()); + + reader.Init(signedMessage.data(), size); + + // SignedData ::= SEQUENCE + ASN1_PARSE_ENTER_SEQUENCE + { + // id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 } + // OID identifies the CMS signed-data content type + ASN1_PARSE_ANY; + + // version [0] EXPLICIT Version DEFAULT v3 + ASN1_PARSE_ENTER_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) + { + ASN1_PARSE_ENTER_SEQUENCE + { + // Version ::= INTEGER { v3(3) } + ASN1_PARSE_ANY; + + // digestAlgorithm OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1) + ASN1_PARSE_ANY; + + // encapContentInfo EncapsulatedContentInfo + ReturnErrorOnFailure(DecodeEncapsulatedContent(reader, cdContent)); + + // signerInfo SignerInfo + ASN1_PARSE_ANY; + } + ASN1_EXIT_SEQUENCE; + } + ASN1_EXIT_CONSTRUCTED; + } + ASN1_EXIT_SEQUENCE; + +exit: + return err; +} + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/CertificationDeclaration.h b/src/credentials/CertificationDeclaration.h new file mode 100644 index 00000000000000..2dbe72617d4bf7 --- /dev/null +++ b/src/credentials/CertificationDeclaration.h @@ -0,0 +1,134 @@ +/* + * + * 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 defines data types, objects and APIs for + * working with Certification Declaration elements. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace chip { +namespace Credentials { + +static constexpr uint32_t kMaxProductIdsCountPerCD = 100; + +static constexpr uint32_t kCertificationElements_TLVEncodedMaxLength = (1 + 1) + // Length of header and end of outer TLV structure. + (1 + sizeof(uint16_t)) * kMaxProductIdsCountPerCD + 3 + // Max encoding length of an array of 100 uint16_t elements. + (2 + sizeof(uint8_t)) * 2 + // Encoding length of two uint8_t element. + (2 + sizeof(uint16_t)) * 5; // Max total encoding length of five uint16_t elements. + +static constexpr uint32_t kMaxCMSSignedCDMessage = 183 + kCertificationElements_TLVEncodedMaxLength; + +struct CertificationElements +{ + uint16_t VendorId; + uint16_t ProductIds[kMaxProductIdsCountPerCD]; + uint8_t ProductIdsCount; + uint16_t ServerCategoryId; + uint16_t ClientCategoryId; + uint8_t SecurityLevel; + uint16_t SecurityInformation; + uint16_t VersionNumber; + uint8_t CertificationType; +}; + +/** + * @brief Encode certification elements in TLV format. + * + * @param[in] certElements Certification elements to encode. + * @param[out] encodedCertElements A byte span to write to the TLV encoded certification elements. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR EncodeCertificationElements(const CertificationElements & certElements, MutableByteSpan & encodedCertElements); + +/** + * @brief Decode certification elements from TLV encoded structure. + * + * @param[in] encodedCertElements A byte span to read the TLV encoded certification elements. + * @param[out] certElements Decoded certification elements. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, CertificationElements & certElements); + +/** + * @brief Generate CMS signed message. + * + * @param[in] cdContent A byte span with Certification Declaration TLV encoded content. + * @param[in] signerKeyId A byte span with the signer key identifier. + * @param[in] signerKeypair A reference to keypair used to sign the message. + * @param[out] signedMessage A byte span to hold a signed CMS message. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR CMS_Sign(const ByteSpan & cdContent, const ByteSpan & signerKeyId, Crypto::P256Keypair & signerKeypair, + MutableByteSpan & signedMessage); + +/** + * @brief Verify CMS signed message. + * + * @param[in] signedMessage A byte span with CMS signed message. + * @param[in] signerX509Cert A byte span with the signer certificate in X509 form. + * @param[out] cdContent A byte span to hold a CD content extracted from the CMS signed message. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR CMS_Verify(const ByteSpan & signedMessage, const ByteSpan & signerX509Cert, ByteSpan & cdContent); + +/** + * @brief Verify CMS signed message. + * + * @param[in] signedMessage A byte span with CMS signed message. + * @param[in] signerPubkey A reference to public key associated with the private key that was used to sign the message. + * @param[out] cdContent A byte span to hold a CD content extracted from the CMS signed message. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR CMS_Verify(const ByteSpan & signedMessage, const Crypto::P256PublicKey & signerPubkey, ByteSpan & cdContent); + +/** + * @brief Extract Certification Declaration content from the CMS signed message. + * + * @param[in] signedMessage A byte span with CMS signed message. + * @param[out] cdContent A byte span to hold a CD content extracted from the CMS signed message. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR CMS_ExtractCDContent(const ByteSpan & signedMessage, ByteSpan & cdContent); + +/** + * @brief Extract key identifier from the CMS signed message. + * + * @param[in] signedMessage A byte span with CMS signed message. + * @param[out] signerKeyId A byte span to hold a key identifier extracted from the CMS signed message. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR CMS_ExtractKeyId(const ByteSpan & signedMessage, ByteSpan & signerKeyId); + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/tests/BUILD.gn b/src/credentials/tests/BUILD.gn index 3b1b24e02e0fa1..99a77f05ec01a3 100644 --- a/src/credentials/tests/BUILD.gn +++ b/src/credentials/tests/BUILD.gn @@ -37,6 +37,7 @@ chip_test_suite("tests") { output_dir = "${root_out_dir}/lib" test_sources = [ + "TestCertificationDeclaration.cpp", "TestChipCert.cpp", "TestDeviceAttestationConstruction.cpp", "TestDeviceAttestationCredentials.cpp", diff --git a/src/credentials/tests/TestCertificationDeclaration.cpp b/src/credentials/tests/TestCertificationDeclaration.cpp new file mode 100644 index 00000000000000..85dfe90a2d2d93 --- /dev/null +++ b/src/credentials/tests/TestCertificationDeclaration.cpp @@ -0,0 +1,239 @@ +/* + * + * 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 a unit test suite for CHIP Certification + * declaration classes and APIs. + */ + +#include +#include + +#include +#include +#include + +#include + +using namespace chip; +using namespace chip::ASN1; +using namespace chip::Crypto; +using namespace chip::Credentials; + +static constexpr uint8_t sTestCMS_SignerCert[] = { + 0x30, 0x82, 0x01, 0xa7, 0x30, 0x82, 0x01, 0x4d, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x74, 0x1f, 0x94, 0x28, 0x05, 0x8f, + 0x11, 0xa6, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x26, 0x31, 0x24, 0x30, 0x22, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1b, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x44, 0x20, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x20, 0x17, 0x0d, 0x32, 0x31, 0x30, 0x36, 0x32, + 0x38, 0x31, 0x34, 0x32, 0x33, 0x34, 0x33, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, + 0x39, 0x35, 0x39, 0x5a, 0x30, 0x26, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1b, 0x4d, 0x61, 0x74, 0x74, + 0x65, 0x72, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x44, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xd7, 0x35, 0x94, 0xc9, 0x7b, 0xb4, 0x7c, 0xb4, 0x35, 0x8b, 0xa5, 0x8e, 0xf9, + 0x6f, 0x80, 0x49, 0xcb, 0xc8, 0x14, 0xb5, 0xdb, 0xd3, 0x1a, 0xe4, 0x73, 0xd5, 0x57, 0x74, 0x77, 0x55, 0xed, 0xa1, 0xd7, 0x54, + 0x7a, 0xe6, 0x0f, 0xaa, 0xa3, 0xd3, 0xc7, 0x2d, 0xbe, 0x44, 0x73, 0xab, 0x3b, 0x72, 0x08, 0x6d, 0xe5, 0x12, 0x2b, 0x2a, 0x63, + 0x72, 0x4e, 0xfe, 0x9b, 0xdb, 0x84, 0xdb, 0x92, 0xe4, 0xa3, 0x63, 0x30, 0x61, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, + 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, + 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xfd, 0x03, 0xc3, 0x49, 0xfc, 0x32, + 0x9e, 0x6c, 0xef, 0xf0, 0x1b, 0xa7, 0x7f, 0x6b, 0x8a, 0x31, 0xfb, 0xc0, 0xe7, 0xd4, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, + 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xfd, 0x03, 0xc3, 0x49, 0xfc, 0x32, 0x9e, 0x6c, 0xef, 0xf0, 0x1b, 0xa7, 0x7f, 0x6b, 0x8a, + 0x31, 0xfb, 0xc0, 0xe7, 0xd4, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, + 0x45, 0x02, 0x20, 0x19, 0x02, 0xe4, 0xce, 0x75, 0x91, 0x8b, 0x25, 0xff, 0xbb, 0xb5, 0x1c, 0x80, 0x13, 0x6f, 0xd8, 0x65, 0x71, + 0x10, 0x42, 0x23, 0x85, 0x5a, 0x6c, 0x8a, 0x95, 0x8f, 0xf5, 0x47, 0x17, 0x07, 0x11, 0x02, 0x21, 0x00, 0xc3, 0x2b, 0x9c, 0x0d, + 0x15, 0x5b, 0x4e, 0xf5, 0x0c, 0xa0, 0xf2, 0x25, 0x34, 0x13, 0xaa, 0x24, 0xcc, 0xc6, 0xbd, 0x97, 0xed, 0xea, 0x31, 0x75, 0x80, + 0x52, 0x2d, 0x26, 0xf1, 0xc1, 0x9b, 0x93 +}; + +static constexpr uint8_t sTestCMS_SignedMessage[] = { + 0x30, 0x81, 0xDB, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02, 0xA0, 0x81, 0xCD, 0x30, 0x81, 0xCA, 0x02, + 0x01, 0x03, 0x31, 0x0D, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x37, 0x06, 0x09, + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x2A, 0x04, 0x28, 0x15, 0x25, 0x01, 0xF1, 0xFF, 0x36, 0x02, 0x05, + 0x00, 0x80, 0x05, 0x01, 0x80, 0x05, 0x02, 0x80, 0x18, 0x25, 0x03, 0xD2, 0x04, 0x25, 0x04, 0x2E, 0x16, 0x24, 0x05, 0xAA, 0x25, + 0x06, 0xDE, 0xC0, 0x25, 0x07, 0x94, 0x26, 0x24, 0x08, 0x00, 0x18, 0x31, 0x7D, 0x30, 0x7B, 0x02, 0x01, 0x03, 0x80, 0x14, 0xFD, + 0x03, 0xC3, 0x49, 0xFC, 0x32, 0x9E, 0x6C, 0xEF, 0xF0, 0x1B, 0xA7, 0x7F, 0x6B, 0x8A, 0x31, 0xFB, 0xC0, 0xE7, 0xD4, 0x30, 0x0B, + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, + 0x03, 0x02, 0x04, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0x88, 0x16, 0xB6, 0x1E, 0x5B, 0x43, 0x90, 0x00, 0xA7, 0x42, 0x07, 0xB2, + 0x6A, 0xBB, 0x0A, 0xB6, 0x6C, 0xAA, 0xCB, 0xDD, 0x05, 0x08, 0xE8, 0xE3, 0xE2, 0xC6, 0xAB, 0x0F, 0x41, 0x29, 0x73, 0xCE, 0x02, + 0x20, 0x0C, 0x09, 0xED, 0x4F, 0x05, 0x43, 0x10, 0x17, 0xA9, 0xC4, 0xB5, 0x00, 0x5F, 0x23, 0x49, 0x25, 0x01, 0x2D, 0xAB, 0x56, + 0x34, 0x75, 0xE4, 0x7D, 0x0C, 0x82, 0x42, 0xF7, 0xAD, 0x21, 0xA1, 0xFF +}; + +static constexpr uint8_t sTestCMS_CDContent[] = { 0x15, 0x25, 0x01, 0xF1, 0xFF, 0x36, 0x02, 0x05, 0x00, 0x80, + 0x05, 0x01, 0x80, 0x05, 0x02, 0x80, 0x18, 0x25, 0x03, 0xD2, + 0x04, 0x25, 0x04, 0x2E, 0x16, 0x24, 0x05, 0xAA, 0x25, 0x06, + 0xDE, 0xC0, 0x25, 0x07, 0x94, 0x26, 0x24, 0x08, 0x00, 0x18 }; + +static constexpr uint8_t sTestCMS_SignerKeyId[] = { 0xfd, 0x03, 0xc3, 0x49, 0xfc, 0x32, 0x9e, 0x6c, 0xef, 0xf0, + 0x1b, 0xa7, 0x7f, 0x6b, 0x8a, 0x31, 0xfb, 0xc0, 0xe7, 0xd4 }; + +// The value of the private key is: +// 0xbb, 0xd0, 0xe5, 0xa9, 0x97, 0x99, 0x50, 0xa6, 0x1a, 0xe5, 0xfe, 0xa8, 0xcc, 0x5d, 0xbc, 0x2c, +// 0xb0, 0xa4, 0x3f, 0xed, 0xcf, 0xfa, 0x2b, 0x68, 0xe3, 0x09, 0x4a, 0xf1, 0x00, 0x1c, 0xee, 0x41 +static constexpr uint8_t sTestCMS_SerializedKeypair[] = { + 0x04, 0xd7, 0x35, 0x94, 0xc9, 0x7b, 0xb4, 0x7c, 0xb4, 0x35, 0x8b, 0xa5, 0x8e, 0xf9, 0x6f, 0x80, 0x49, 0xcb, 0xc8, 0x14, + 0xb5, 0xdb, 0xd3, 0x1a, 0xe4, 0x73, 0xd5, 0x57, 0x74, 0x77, 0x55, 0xed, 0xa1, 0xd7, 0x54, 0x7a, 0xe6, 0x0f, 0xaa, 0xa3, + 0xd3, 0xc7, 0x2d, 0xbe, 0x44, 0x73, 0xab, 0x3b, 0x72, 0x08, 0x6d, 0xe5, 0x12, 0x2b, 0x2a, 0x63, 0x72, 0x4e, 0xfe, 0x9b, + 0xdb, 0x84, 0xdb, 0x92, 0xe4, 0xbb, 0xd0, 0xe5, 0xa9, 0x97, 0x99, 0x50, 0xa6, 0x1a, 0xe5, 0xfe, 0xa8, 0xcc, 0x5d, 0xbc, + 0x2c, 0xb0, 0xa4, 0x3f, 0xed, 0xcf, 0xfa, 0x2b, 0x68, 0xe3, 0x09, 0x4a, 0xf1, 0x00, 0x1c, 0xee, 0x41 +}; + +static constexpr uint8_t sTestCMS_SignerPublicKey[] = { 0x04, 0xd7, 0x35, 0x94, 0xc9, 0x7b, 0xb4, 0x7c, 0xb4, 0x35, 0x8b, + 0xa5, 0x8e, 0xf9, 0x6f, 0x80, 0x49, 0xcb, 0xc8, 0x14, 0xb5, 0xdb, + 0xd3, 0x1a, 0xe4, 0x73, 0xd5, 0x57, 0x74, 0x77, 0x55, 0xed, 0xa1, + 0xd7, 0x54, 0x7a, 0xe6, 0x0f, 0xaa, 0xa3, 0xd3, 0xc7, 0x2d, 0xbe, + 0x44, 0x73, 0xab, 0x3b, 0x72, 0x08, 0x6d, 0xe5, 0x12, 0x2b, 0x2a, + 0x63, 0x72, 0x4e, 0xfe, 0x9b, 0xdb, 0x84, 0xdb, 0x92, 0xe4 }; + +static void TestCertificationElements(nlTestSuite * inSuite, void * inContext) +{ + CertificationElements certElementsIn1 = { + 0x4567, { 0xFA23, 0x24, 0xFA25, 0x1234 }, 4, 0x1BC8, 0xFFAA, 0xFF, 0x01, 0xCC44, 0x01 + }; + CertificationElements certElementsOut; + + uint8_t encodedCertElemBuf[kCertificationElements_TLVEncodedMaxLength]; + MutableByteSpan encodedCertElem1Span(encodedCertElemBuf); + + NL_TEST_ASSERT(inSuite, EncodeCertificationElements(certElementsIn1, encodedCertElem1Span) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, DecodeCertificationElements(encodedCertElem1Span, certElementsOut) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, certElementsOut.VendorId == certElementsIn1.VendorId); + NL_TEST_ASSERT(inSuite, certElementsOut.ProductIdsCount == certElementsIn1.ProductIdsCount); + for (uint8_t i = 0; i < certElementsOut.ProductIdsCount; i++) + { + NL_TEST_ASSERT(inSuite, certElementsOut.ProductIds[i] == certElementsIn1.ProductIds[i]); + } + NL_TEST_ASSERT(inSuite, certElementsOut.ServerCategoryId == certElementsIn1.ServerCategoryId); + NL_TEST_ASSERT(inSuite, certElementsOut.ClientCategoryId == certElementsIn1.ClientCategoryId); + NL_TEST_ASSERT(inSuite, certElementsOut.SecurityLevel == certElementsIn1.SecurityLevel); + NL_TEST_ASSERT(inSuite, certElementsOut.SecurityInformation == certElementsIn1.SecurityInformation); + NL_TEST_ASSERT(inSuite, certElementsOut.VersionNumber == certElementsIn1.VersionNumber); + NL_TEST_ASSERT(inSuite, certElementsOut.CertificationType == certElementsIn1.CertificationType); + + // Test precomputed TLV encoded structure with the following paramenters: + CertificationElements certElementsIn2 = { 0xFFF1, { 0x8000, 0x8001, 0x8002 }, 3, 0x04D2, 0x162E, 0xAA, 0xC0DE, 0x2694, 0x00 }; + ByteSpan encodedCertElem2Span(sTestCMS_CDContent); + + NL_TEST_ASSERT(inSuite, DecodeCertificationElements(encodedCertElem2Span, certElementsOut) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, certElementsOut.VendorId == certElementsIn2.VendorId); + NL_TEST_ASSERT(inSuite, certElementsOut.ProductIdsCount == certElementsIn2.ProductIdsCount); + for (uint8_t i = 0; i < certElementsOut.ProductIdsCount; i++) + { + NL_TEST_ASSERT(inSuite, certElementsOut.ProductIds[i] == certElementsIn2.ProductIds[i]); + } + NL_TEST_ASSERT(inSuite, certElementsOut.ServerCategoryId == certElementsIn2.ServerCategoryId); + NL_TEST_ASSERT(inSuite, certElementsOut.ClientCategoryId == certElementsIn2.ClientCategoryId); + NL_TEST_ASSERT(inSuite, certElementsOut.SecurityLevel == certElementsIn2.SecurityLevel); + NL_TEST_ASSERT(inSuite, certElementsOut.SecurityInformation == certElementsIn2.SecurityInformation); + NL_TEST_ASSERT(inSuite, certElementsOut.VersionNumber == certElementsIn2.VersionNumber); + NL_TEST_ASSERT(inSuite, certElementsOut.CertificationType == certElementsIn2.CertificationType); + + MutableByteSpan encodedCertElem3Span(encodedCertElemBuf); + NL_TEST_ASSERT(inSuite, EncodeCertificationElements(certElementsOut, encodedCertElem3Span) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, encodedCertElem3Span.data_equal(encodedCertElem2Span)); + + // Test Encode Error: CHIP_ERROR_BUFFER_TOO_SMALL + encodedCertElem3Span.reduce_size(encodedCertElem3Span.size() - 4); + NL_TEST_ASSERT(inSuite, EncodeCertificationElements(certElementsIn1, encodedCertElem3Span) == CHIP_ERROR_BUFFER_TOO_SMALL); + + // Test Decode Error: CHIP_ERROR_INVALID_TLV_ELEMENT + // manually modified sTestCMS_CDContent[] with larger ServerCategoryId value (4-octet) + uint8_t encodedCertElem4[] = { 0x15, 0x25, 0x01, 0xF1, 0xFF, 0x36, 0x02, 0x05, 0x00, 0x80, 0x05, 0x01, 0x80, 0x05, + 0x02, 0x80, 0x18, 0x26, 0x03, 0xD2, 0x04, 0x01, 0x00, 0x25, 0x04, 0x2E, 0x16, 0x24, + 0x05, 0xAA, 0x25, 0x06, 0xDE, 0xC0, 0x25, 0x07, 0x94, 0x26, 0x24, 0x08, 0x00, 0x18 }; + + ByteSpan encodedCertElem4Span(encodedCertElem4); + NL_TEST_ASSERT(inSuite, DecodeCertificationElements(encodedCertElem4Span, certElementsOut) == CHIP_ERROR_INVALID_TLV_ELEMENT); + + // Test Decode Error: CHIP_ERROR_UNEXPECTED_TLV_ELEMENT + // manually modified sTestCMS_CDContent[] switched elements order ProductIds <--> ServerCategoryId + uint8_t encodedCertElem5[] = { 0x15, 0x25, 0x01, 0xF1, 0xFF, 0x36, 0x25, 0x03, 0xD2, 0x04, 0x02, 0x05, 0x00, 0x80, + 0x05, 0x01, 0x80, 0x05, 0x02, 0x80, 0x18, 0x25, 0x04, 0x2E, 0x16, 0x24, 0x05, 0xAA, + 0x25, 0x06, 0xDE, 0xC0, 0x25, 0x07, 0x94, 0x26, 0x24, 0x08, 0x00, 0x18 }; + ByteSpan encodedCertElem5Span(encodedCertElem5); + NL_TEST_ASSERT(inSuite, + DecodeCertificationElements(encodedCertElem5Span, certElementsOut) == CHIP_ERROR_UNEXPECTED_TLV_ELEMENT); +} + +static void TestCMSSignVerify(nlTestSuite * inSuite, void * inContext) +{ + ByteSpan cdContent(sTestCMS_CDContent); + ByteSpan cdContentOut; + ByteSpan signerKeyId(sTestCMS_SignerKeyId); + uint8_t signedMessageBuf[kMaxCMSSignedCDMessage]; + MutableByteSpan signedMessage(signedMessageBuf); + + // Test with random key + P256Keypair keypair; + NL_TEST_ASSERT(inSuite, keypair.Initialize() == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, CMS_Sign(cdContent, signerKeyId, keypair, signedMessage) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, CMS_Verify(signedMessage, keypair.Pubkey(), cdContentOut) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, cdContent.data_equal(cdContentOut)); + + // Test with known key + P256Keypair keypair2; + P256SerializedKeypair serializedKeypair; + memcpy(serializedKeypair, sTestCMS_SerializedKeypair, sizeof(sTestCMS_SerializedKeypair)); + serializedKeypair.SetLength(sizeof(sTestCMS_SerializedKeypair)); + signedMessage = MutableByteSpan(signedMessageBuf); + NL_TEST_ASSERT(inSuite, keypair2.Deserialize(serializedKeypair) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, CMS_Sign(cdContent, signerKeyId, keypair2, signedMessage) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, CMS_Verify(signedMessage, keypair2.Pubkey(), cdContentOut) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, cdContent.data_equal(cdContentOut)); + + // Test known CMS test vector + ByteSpan signedTestMessage(sTestCMS_SignedMessage); + P256PublicKey signerPubkey(sTestCMS_SignerPublicKey); + cdContentOut = ByteSpan(); + NL_TEST_ASSERT(inSuite, CMS_Verify(signedTestMessage, signerPubkey, cdContentOut) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, cdContent.data_equal(cdContentOut)); + + // Test known CMS test vector with X509 Certificate. + ByteSpan signerX509Cert(sTestCMS_SignerCert); + cdContentOut = ByteSpan(); + NL_TEST_ASSERT(inSuite, CMS_Verify(signedTestMessage, signerX509Cert, cdContentOut) == CHIP_NO_ERROR); + + // TestCMS_ExtractCDContent() + cdContentOut = ByteSpan(); + NL_TEST_ASSERT(inSuite, CMS_ExtractCDContent(signedTestMessage, cdContentOut) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, cdContent.data_equal(cdContentOut)); + + // TestCMS_ExtractKeyId() + ByteSpan signerKeyIdOut; + NL_TEST_ASSERT(inSuite, CMS_ExtractKeyId(signedTestMessage, signerKeyIdOut) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, signerKeyId.data_equal(signerKeyIdOut)); +} + +#define NL_TEST_DEF_FN(fn) NL_TEST_DEF("Test " #fn, fn) +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { NL_TEST_DEF_FN(TestCertificationElements), NL_TEST_DEF_FN(TestCMSSignVerify), NL_TEST_SENTINEL() }; + +int TestCertificationDeclaration(void) +{ + nlTestSuite theSuite = { "CHIP Certification Declaration tests", &sTests[0], nullptr, nullptr }; + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestCertificationDeclaration);