diff --git a/examples/all-clusters-app/linux/AppOptions.cpp b/examples/all-clusters-app/linux/AppOptions.cpp index c3f80a2302a484..ef42660972f8d9 100644 --- a/examples/all-clusters-app/linux/AppOptions.cpp +++ b/examples/all-clusters-app/linux/AppOptions.cpp @@ -21,6 +21,8 @@ #include #include +using namespace chip::ArgParser; + using chip::ArgParser::OptionDef; using chip::ArgParser::OptionSet; using chip::ArgParser::PrintArgError; @@ -55,8 +57,8 @@ bool AppOptions::HandleOptions(const char * program, OptionSet * options, int id OptionSet * AppOptions::GetOptions() { static OptionDef optionsDef[] = { - { "dac_provider", chip::ArgParser::kArgumentRequired, kOptionDacProviderFilePath }, - { "min_commissioning_timeout", chip::ArgParser::kArgumentRequired, kOptionMinCommissioningTimeout }, + { "dac_provider", kArgumentRequired, kOptionDacProviderFilePath }, + { "min_commissioning_timeout", kArgumentRequired, kOptionMinCommissioningTimeout }, {}, }; diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 001abb074bd530..640d5230b9e2cf 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -39,6 +39,8 @@ source_set("app-main") { "LinuxCommissionableDataProvider.h", "Options.cpp", "Options.h", + "testing/CustomCSRResponse.cpp", + "testing/CustomCSRResponse.h", ] defines = [] diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 03b355c23735a5..c8bae87c377e14 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -39,29 +39,36 @@ LinuxDeviceOptions gDeviceOptions; // Follow the code style of command line arguments in case we need to add more options in the future. enum { - kDeviceOption_BleDevice = 0x1000, - kDeviceOption_WiFi = 0x1001, - kDeviceOption_Thread = 0x1002, - kDeviceOption_Version = 0x1003, - kDeviceOption_VendorID = 0x1004, - kDeviceOption_ProductID = 0x1005, - kDeviceOption_CustomFlow = 0x1006, - kDeviceOption_Capabilities = 0x1007, - kDeviceOption_Discriminator = 0x1008, - kDeviceOption_Passcode = 0x1009, - kDeviceOption_SecuredDevicePort = 0x100a, - kDeviceOption_SecuredCommissionerPort = 0x100b, - kDeviceOption_UnsecuredCommissionerPort = 0x100c, - kDeviceOption_Command = 0x100d, - kDeviceOption_PICS = 0x100e, - kDeviceOption_KVS = 0x100f, - kDeviceOption_InterfaceId = 0x1010, - kDeviceOption_Spake2pVerifierBase64 = 0x1011, - kDeviceOption_Spake2pSaltBase64 = 0x1012, - kDeviceOption_Spake2pIterations = 0x1013, - kDeviceOption_TraceFile = 0x1014, - kDeviceOption_TraceLog = 0x1015, - kDeviceOption_TraceDecode = 0x1016, + kDeviceOption_BleDevice = 0x1000, + kDeviceOption_WiFi = 0x1001, + kDeviceOption_Thread = 0x1002, + kDeviceOption_Version = 0x1003, + kDeviceOption_VendorID = 0x1004, + kDeviceOption_ProductID = 0x1005, + kDeviceOption_CustomFlow = 0x1006, + kDeviceOption_Capabilities = 0x1007, + kDeviceOption_Discriminator = 0x1008, + kDeviceOption_Passcode = 0x1009, + kDeviceOption_SecuredDevicePort = 0x100a, + kDeviceOption_SecuredCommissionerPort = 0x100b, + kDeviceOption_UnsecuredCommissionerPort = 0x100c, + kDeviceOption_Command = 0x100d, + kDeviceOption_PICS = 0x100e, + kDeviceOption_KVS = 0x100f, + kDeviceOption_InterfaceId = 0x1010, + kDeviceOption_Spake2pVerifierBase64 = 0x1011, + kDeviceOption_Spake2pSaltBase64 = 0x1012, + kDeviceOption_Spake2pIterations = 0x1013, + kDeviceOption_TraceFile = 0x1014, + kDeviceOption_TraceLog = 0x1015, + kDeviceOption_TraceDecode = 0x1016, + kOptionCSRResponseCSRIncorrectType = 0x1017, + kOptionCSRResponseCSRNonceIncorrectType = 0x1018, + kOptionCSRResponseCSRNonceTooLong = 0x1019, + kOptionCSRResponseCSRNonceInvalid = 0x101a, + kOptionCSRResponseNOCSRElementsTooLong = 0x101b, + kOptionCSRResponseAttestationSignatureIncorrectType = 0x101c, + kOptionCSRResponseAttestationSignatureInvalid = 0x101d, }; constexpr unsigned kAppUsageLength = 64; @@ -98,6 +105,13 @@ OptionDef sDeviceOptionDefs[] = { { "trace_log", kArgumentRequired, kDeviceOption_TraceLog }, { "trace_decode", kArgumentRequired, kDeviceOption_TraceDecode }, #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + { "cert_error_csr_incorrect_type", kNoArgument, kOptionCSRResponseCSRIncorrectType }, + { "cert_error_csr_nonce_incorrect_type", kNoArgument, kOptionCSRResponseCSRNonceIncorrectType }, + { "cert_error_csr_nonce_too_long", kNoArgument, kOptionCSRResponseCSRNonceTooLong }, + { "cert_error_csr_nonce_invalid", kNoArgument, kOptionCSRResponseCSRNonceInvalid }, + { "cert_error_nocsrelements_too_long", kNoArgument, kOptionCSRResponseNOCSRElementsTooLong }, + { "cert_error_attestation_signature_incorrect_type", kNoArgument, kOptionCSRResponseAttestationSignatureIncorrectType }, + { "cert_error_attestation_signature_invalid", kNoArgument, kOptionCSRResponseAttestationSignatureInvalid }, {} }; @@ -183,6 +197,20 @@ const char * sDeviceOptionHelp = " --trace_decode <1/0>\n" " A value of 1 enables traces decoding, 0 disables this (default 0).\n" #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + " --cert_error_csr_incorrect_type\n" + " Configure the CSRResponse to be built with an invalid CSR type.\n" + " --cert_error_csr_nonce_incorrect_type\n" + " Configure the CSRResponse to be built with an invalid CSRNonce type.\n" + " --cert_error_csr_nonce_too_long\n" + " Configure the CSRResponse to be built with a CSRNonce that is longer than expected.\n" + " --cert_error_csr_nonce_invalid\n" + " Configure the CSRResponse to be built with a CSRNonce that does not match the CSRNonce from the CSRRequest.\n" + " --cert_error_nocsrelements_too_long\n" + " Configure the CSRResponse to contains an NOCSRElements larger than the allowed RESP_MAX.\n" + " --cert_error_attestation_signature_incorrect_type\n" + " Configure the CSRResponse to be build with an invalid AttestationSignature type.\n" + " --cert_error_attestation_signature_invalid\n" + " Configure the CSRResponse to be build with an AttestationSignature that does not match what is expected.\n" "\n"; bool Base64ArgToVector(const char * arg, size_t maxSize, std::vector & outVector) @@ -386,6 +414,28 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, break; #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + case kOptionCSRResponseCSRIncorrectType: + LinuxDeviceOptions::GetInstance().mCSRResponseOptions.csrIncorrectType = true; + break; + case kOptionCSRResponseCSRNonceIncorrectType: + LinuxDeviceOptions::GetInstance().mCSRResponseOptions.csrNonceIncorrectType = true; + break; + case kOptionCSRResponseCSRNonceTooLong: + LinuxDeviceOptions::GetInstance().mCSRResponseOptions.csrNonceTooLong = true; + break; + case kOptionCSRResponseCSRNonceInvalid: + LinuxDeviceOptions::GetInstance().mCSRResponseOptions.csrNonceInvalid = true; + break; + case kOptionCSRResponseNOCSRElementsTooLong: + LinuxDeviceOptions::GetInstance().mCSRResponseOptions.nocsrElementsTooLong = true; + break; + case kOptionCSRResponseAttestationSignatureIncorrectType: + LinuxDeviceOptions::GetInstance().mCSRResponseOptions.attestationSignatureIncorrectType = true; + break; + case kOptionCSRResponseAttestationSignatureInvalid: + LinuxDeviceOptions::GetInstance().mCSRResponseOptions.attestationSignatureInvalid = true; + break; + default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", aProgram, aName); retval = false; diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h index ca588dc6e54a5b..56a95b772cafd8 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -35,6 +35,7 @@ #include #include +#include struct LinuxDeviceOptions { @@ -57,6 +58,7 @@ struct LinuxDeviceOptions bool traceStreamToLogEnabled = false; chip::Optional traceStreamFilename; chip::Credentials::DeviceAttestationCredentialsProvider * dacProvider = nullptr; + chip::CSRResponseOptions mCSRResponseOptions; static LinuxDeviceOptions & GetInstance(); }; diff --git a/examples/platform/linux/testing/CustomCSRResponse.cpp b/examples/platform/linux/testing/CustomCSRResponse.cpp new file mode 100644 index 00000000000000..39f7e9d2cad6c7 --- /dev/null +++ b/examples/platform/linux/testing/CustomCSRResponse.cpp @@ -0,0 +1,165 @@ +/* + * + * Copyright (c) 2022 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. + */ + +#include "Options.h" + +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters::OperationalCredentials::Commands; + +constexpr size_t kMaxResponseLength = 900; +constexpr size_t kCSRNonceLength = 32; + +namespace { + +CHIP_ERROR ConstructCustomNOCSRElements(TLV::TLVWriter & writer, TLV::Tag tag, const ByteSpan & nocsrElements, + CSRResponseOptions & options) +{ + ByteSpan csr; + ByteSpan csrNonce; + ByteSpan vendorReserved1; + ByteSpan vendorReserved2; + ByteSpan vendorReserved3; + ReturnErrorOnFailure( + Credentials::DeconstructNOCSRElements(nocsrElements, csr, csrNonce, vendorReserved1, vendorReserved2, vendorReserved3)); + + // Add 10 bytes of possible overhead to allow the generation of content longer than the allowed maximum of RESP_MAX. + // 10 has been choosen to leave enough space for the possible TLV overhead when adding the additional data. + uint8_t nocsrElementsData[kMaxResponseLength + 10]; + MutableByteSpan nocsrElementsSpan(nocsrElementsData); + + TLV::TLVType outerContainerType = TLV::kTLVType_NotSpecified; + TLV::TLVWriter tlvWriter; + tlvWriter.Init(nocsrElementsSpan); + + ReturnErrorOnFailure(tlvWriter.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerContainerType)); + + // Update CSR + if (options.csrIncorrectType) + { + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(1), true)); + } + else + { + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(1), csr)); + } + + // Update CSRNonce + if (options.csrNonceIncorrectType) + { + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(2), true)); + } + else if (options.csrNonceInvalid) + { + uint8_t csrNonceInvalid[kCSRNonceLength] = {}; + memcpy(csrNonceInvalid, csrNonce.data(), csrNonce.size()); + std::reverse(csrNonceInvalid, csrNonceInvalid + sizeof(csrNonceInvalid)); + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(2), ByteSpan(csrNonceInvalid))); + } + else if (options.csrNonceTooLong) + { + uint8_t csrNonceTooLong[kCSRNonceLength + 1] = {}; + memcpy(csrNonceTooLong, csrNonce.data(), csrNonce.size()); + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(2), ByteSpan(csrNonceTooLong))); + } + else + { + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(2), csrNonce)); + } + + // Add vendorReserved1 if present + if (!vendorReserved1.empty()) + { + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(3), vendorReserved1)); + } + + // Add vendorReserved2 if present + if (!vendorReserved2.empty()) + { + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(4), vendorReserved2)); + } + + // Add vendorReserved3 if present + if (!vendorReserved3.empty()) + { + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(5), vendorReserved3)); + } + + // Add additional data + if (options.nocsrElementsTooLong) + { + size_t len = kMaxResponseLength - tlvWriter.GetLengthWritten(); + ReturnLogErrorOnFailure(tlvWriter.Put(TLV::ContextTag(6), ByteSpan(nocsrElementsData, len))); + } + + ReturnErrorOnFailure(tlvWriter.EndContainer(outerContainerType)); + ReturnErrorOnFailure(tlvWriter.Finalize()); + + return DataModel::Encode(writer, tag, nocsrElementsSpan.SubSpan(0, tlvWriter.GetLengthWritten())); +} + +CHIP_ERROR ConstructCustomAttestationSignature(TLV::TLVWriter & writer, TLV::Tag tag, const ByteSpan & attestationSignature, + CSRResponseOptions & options) +{ + if (options.attestationSignatureIncorrectType) + { + return DataModel::Encode(writer, tag, true); + } + + if (options.attestationSignatureInvalid) + { + uint8_t invalidAttestationSignature[Crypto::kP256_ECDSA_Signature_Length_Raw] = {}; + memcpy(invalidAttestationSignature, attestationSignature.data(), attestationSignature.size()); + std::reverse(invalidAttestationSignature, invalidAttestationSignature + sizeof(invalidAttestationSignature)); + return DataModel::Encode(writer, tag, ByteSpan(invalidAttestationSignature)); + } + + return DataModel::Encode(writer, tag, attestationSignature); +} + +} // namespace + +namespace chip { +namespace app { +namespace DataModel { + +template <> +CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, const CSRResponse::Type & responseData) +{ + auto tag1 = TLV::ContextTag(to_underlying(CSRResponse::Fields::kNOCSRElements)); + auto tag2 = TLV::ContextTag(to_underlying(CSRResponse::Fields::kAttestationSignature)); + auto options = LinuxDeviceOptions::GetInstance().mCSRResponseOptions; + + TLV::TLVType outer; + ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outer)); + ReturnErrorOnFailure(ConstructCustomNOCSRElements(writer, tag1, responseData.NOCSRElements, options)); + ReturnErrorOnFailure(ConstructCustomAttestationSignature(writer, tag2, responseData.attestationSignature, options)); + ReturnErrorOnFailure(writer.EndContainer(outer)); + + return CHIP_NO_ERROR; +} + +} // namespace DataModel +} // namespace app +} // namespace chip diff --git a/examples/platform/linux/testing/CustomCSRResponse.h b/examples/platform/linux/testing/CustomCSRResponse.h new file mode 100644 index 00000000000000..5de00b0646ae6d --- /dev/null +++ b/examples/platform/linux/testing/CustomCSRResponse.h @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2022 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. + */ + +namespace chip { + +struct CSRResponseOptions +{ + bool csrIncorrectType = false; + bool csrNonceIncorrectType = false; + bool csrNonceTooLong = false; + bool csrNonceInvalid = false; + bool nocsrElementsTooLong = false; + bool attestationSignatureIncorrectType = false; + bool attestationSignatureInvalid = false; +}; + +} // namespace chip diff --git a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp index 6524fafa80f836..61172baefb9075 100644 --- a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp +++ b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp @@ -42,6 +42,9 @@ namespace Credentials { namespace { +// As per specifications section 11.22.5.1. Constant RESP_MAX +constexpr size_t kMaxResponseLength = 900; + static const ByteSpan kTestPaaRoots[] = { TestCerts::sTestCert_PAA_FFF1_Cert, TestCerts::sTestCert_PAA_NoVID_Cert, @@ -184,6 +187,9 @@ void DefaultDACVerifier::VerifyAttestationInformation(const DeviceAttestationVer !info.attestationNonceBuffer.empty() && onCompletion != nullptr, attestationError = AttestationVerificationResult::kInvalidArgument); + VerifyOrExit(info.attestationElementsBuffer.size() <= kMaxResponseLength, + attestationError = AttestationVerificationResult::kInvalidArgument); + // match DAC and PAI VIDs { VerifyOrExit(ExtractVIDPIDFromX509Cert(info.dacDerBuffer, dacVidPid) == CHIP_NO_ERROR, @@ -410,6 +416,7 @@ CHIP_ERROR DefaultDACVerifier::VerifyNodeOperationalCSRInformation(const ByteSpa !attestationSignatureBuffer.empty() && !csrNonce.empty(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(nocsrElementsBuffer.size() <= kMaxResponseLength, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(csrNonce.size() == Controller::kCSRNonceLength, CHIP_ERROR_INVALID_ARGUMENT); ByteSpan csrSpan; @@ -420,6 +427,8 @@ CHIP_ERROR DefaultDACVerifier::VerifyNodeOperationalCSRInformation(const ByteSpa ReturnErrorOnFailure(DeconstructNOCSRElements(nocsrElementsBuffer, csrSpan, csrNonceSpan, vendorReserved1Span, vendorReserved2Span, vendorReserved3Span)); + VerifyOrReturnError(csrNonceSpan.size() == Controller::kCSRNonceLength, CHIP_ERROR_INVALID_ARGUMENT); + // Verify that Nonce matches with what we sent VerifyOrReturnError(csrNonceSpan.data_equal(csrNonce), CHIP_ERROR_INVALID_ARGUMENT);