Skip to content

Commit

Permalink
Construct/Deconstruct helpers for Device Attestation (#9544)
Browse files Browse the repository at this point in the history
* Added DeconstructAttestationElements/ConstructAttestationElements
helpers

Moved CopySpantoMutableSpan into src/lib/support/Span.h as a general use
routine.

Added ExtractVIDFromX509Cert and ExtractAKIDFromX509Cert

Changed attestation data to equate with updated spec.

coauthor: restyled io

* Address review comments

Changing naming convention for constants

simpler logic for context tags.

Eliminate boolean array indexed by tag and have boolean flags.

Removed vector for VendorReserved data and changed signature of helper
functions.

* address review comments

use TLV::ContiguousBufferTLVReader
have consecutive context tags
add TODOs for future work
improve documentations on usage in headers
Have seperate tests for deconstruction with/without firmware info.

* change enum type to uint32_t to avoid unsigned to signed conversion errors. Use UINT32_MAX instead of -1.

* removed unneded header

clang-format did a number of reformats

Updated firmwareInfoTestVector

* added TODO about issue #9825

* restyle: whitespace

* changed numbers of errors
  • Loading branch information
Marty Leisner authored Sep 21, 2021
1 parent dbbe48d commit 1240433
Show file tree
Hide file tree
Showing 8 changed files with 703 additions and 29 deletions.
2 changes: 2 additions & 0 deletions src/credentials/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ static_library("credentials") {
"CHIPCertToX509.cpp",
"CHIPOperationalCredentials.cpp",
"CHIPOperationalCredentials.h",
"DeviceAttestationConstructor.cpp",
"DeviceAttestationConstructor.h",
"DeviceAttestationCredsProvider.cpp",
"DeviceAttestationCredsProvider.h",
"DeviceAttestationVerifier.cpp",
Expand Down
193 changes: 193 additions & 0 deletions src/credentials/DeviceAttestationConstructor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* 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 "DeviceAttestationConstructor.h"

#include <lib/core/CHIPTLV.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>

#include <cstdint>

namespace chip {
namespace Credentials {

// context tag positions
enum : uint32_t
{
kCertificationDeclarationTagId = 1,
kAttestationNonceTagId = 2,
kTimestampTagId = 3,
kFirmwareInfoTagId = 4,
};

CHIP_ERROR DeconstructAttestationElements(const ByteSpan & attestationElements, ByteSpan & certificationDeclaration,
ByteSpan & attestationNonce, uint32_t & timestamp, ByteSpan & firmwareInfo,
ByteSpan * vendorReservedArray, size_t & vendorReservedArraySize, uint16_t & vendorId,
uint16_t & profileNum)
{
bool certificationDeclarationExists = false;
bool attestationNonceExists = false;
bool timestampExists = false;
bool firmwareInfoExists = false;
size_t vendorReservedIdx = 0;
uint32_t lastContextTagId = UINT32_MAX;
TLV::ContiguousBufferTLVReader tlvReader;
TLV::TLVType containerType = TLV::kTLVType_Structure;

firmwareInfo = ByteSpan();

tlvReader.Init(attestationElements);
ReturnErrorOnFailure(tlvReader.Next(containerType, TLV::AnonymousTag));
ReturnErrorOnFailure(tlvReader.EnterContainer(containerType));

CHIP_ERROR error = CHIP_NO_ERROR;

// TODO: per conversation with Tennessee, shold be two consecutive loops (rather than one big
// loop, since the contextTags come before the profileTags)
while ((error = tlvReader.Next()) == CHIP_NO_ERROR)
{
uint64_t tag = tlvReader.GetTag();

if (TLV::IsContextTag(tag))
{
switch (TLV::TagNumFromTag(tag))
{
case kCertificationDeclarationTagId:
VerifyOrReturnError(lastContextTagId == UINT32_MAX, CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
VerifyOrReturnError(certificationDeclarationExists == false, CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
ReturnErrorOnFailure(tlvReader.GetByteView(certificationDeclaration));
certificationDeclarationExists = true;
break;
case kAttestationNonceTagId:
VerifyOrReturnError(lastContextTagId == kCertificationDeclarationTagId,
CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
VerifyOrReturnError(attestationNonceExists == false, CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
ReturnErrorOnFailure(tlvReader.GetByteView(attestationNonce));
attestationNonceExists = true;
break;
case kTimestampTagId:
VerifyOrReturnError(lastContextTagId == kAttestationNonceTagId, CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
VerifyOrReturnError(timestampExists == false, CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
ReturnErrorOnFailure(tlvReader.Get(timestamp));
timestampExists = true;
break;
case kFirmwareInfoTagId:
VerifyOrReturnError(lastContextTagId == kTimestampTagId, CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
VerifyOrReturnError(firmwareInfoExists == false, CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
ReturnErrorOnFailure(tlvReader.GetByteView(firmwareInfo));
firmwareInfoExists = true;
break;
default:
return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT;
}

lastContextTagId = TLV::TagNumFromTag(tag);
}
else if (TLV::IsProfileTag(tag))
{
// vendor fields
bool seenProfile = false;
uint16_t currentVendorId;
uint16_t currentProfileNum;

currentVendorId = TLV::VendorIdFromTag(tag);
currentProfileNum = TLV::ProfileNumFromTag(tag);
if (!seenProfile)
{
seenProfile = true;
vendorId = currentVendorId;
profileNum = currentProfileNum;
}
else
{
// TODO: do not check for this - map vendorId and profileNum to each Vendor Reserved entry
// check that vendorId and profileNum match in every Vendor Reserved entry
VerifyOrReturnError(currentVendorId == vendorId && currentProfileNum == profileNum,
CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT);
}

ByteSpan vendorReservedEntry;
ReturnErrorOnFailure(tlvReader.GetByteView(vendorReservedEntry));
VerifyOrReturnError(vendorReservedIdx < vendorReservedArraySize, CHIP_ERROR_NO_MEMORY);
vendorReservedArray[vendorReservedIdx++] = vendorReservedEntry;
}
else
{
return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT;
}
}

vendorReservedArraySize = vendorReservedIdx;

VerifyOrReturnError(error == CHIP_END_OF_TLV, error);
VerifyOrReturnError(lastContextTagId != UINT32_MAX, CHIP_ERROR_MISSING_TLV_ELEMENT);
VerifyOrReturnError(certificationDeclarationExists && attestationNonceExists && timestampExists,
CHIP_ERROR_MISSING_TLV_ELEMENT);

return CHIP_NO_ERROR;
}

// TODO: have independent vendorId and profileNum entries map to each vendor Reserved entry
// Have a class for vendor reserved data, discussed in:
// https://github.com/project-chip/connectedhomeip/issues/9825
CHIP_ERROR ConstructAttestationElements(const ByteSpan & certificationDeclaration, const ByteSpan & attestationNonce,
uint32_t timestamp, const ByteSpan & firmwareInfo, ByteSpan * vendorReservedArray,
size_t vendorReservedArraySize, uint16_t vendorId, uint16_t profileNum,
MutableByteSpan & attestationElements)
{
TLV::TLVWriter tlvWriter;
TLV::TLVType outerContainerType = TLV::kTLVType_NotSpecified;

VerifyOrReturnError(!certificationDeclaration.empty() && !attestationNonce.empty(), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(attestationNonce.size() == 32, CHIP_ERROR_INVALID_ARGUMENT);
if (vendorReservedArraySize != 0)
{
VerifyOrReturnError(vendorReservedArray != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
}

tlvWriter.Init(attestationElements.data(), static_cast<uint32_t>(attestationElements.size()));
outerContainerType = TLV::kTLVType_NotSpecified;
ReturnErrorOnFailure(tlvWriter.StartContainer(TLV::AnonymousTag, TLV::kTLVType_Structure, outerContainerType));
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(1), certificationDeclaration));
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(2), attestationNonce));
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(3), timestamp));
if (!firmwareInfo.empty())
{
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(4), firmwareInfo));
}

uint8_t vendorTagNum = 1;
for (size_t vendorReservedIdx = 0; vendorReservedIdx < vendorReservedArraySize; ++vendorReservedIdx)
{
if (!vendorReservedArray[vendorReservedIdx].empty())
{
ReturnErrorOnFailure(
tlvWriter.Put(TLV::ProfileTag(vendorId, profileNum, vendorTagNum), vendorReservedArray[vendorReservedIdx]));
}
vendorTagNum++;
}

ReturnErrorOnFailure(tlvWriter.EndContainer(outerContainerType));
ReturnErrorOnFailure(tlvWriter.Finalize());
attestationElements = attestationElements.SubSpan(0, tlvWriter.GetLengthWritten());

return CHIP_NO_ERROR;
}

} // namespace Credentials

} // namespace chip
67 changes: 67 additions & 0 deletions src/credentials/DeviceAttestationConstructor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* 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.
*/
#pragma once

#include <lib/core/CHIPError.h>
#include <lib/support/Span.h>

#include <vector>

namespace chip {
namespace Credentials {

/**
* @brief Take the attestation elements buffer and return each component seperately.
* All output data stays valid while attestationElements buffer is valid.
*
* @param[in] attestationElements ByteSpan containg source of Attestation Elements data.
* @param[out] certificationDeclaration
* @param[out] attestationNonce
* @param[out] timestamp
* @param[out] firmwareInfo ByteSpan containing Firmware Information data if present within attestationElements.
* Empty ByteSpan if not present in attestationElements.
* @param[out] vendorReservedArray
* @param[inout] vendorReservedArraySize
* @param[out] vendorId Vendor ID fetched from Attestation Elements data.
* @param[out] profileNum Profile Number fetched from Attestation Elements data.
*/
CHIP_ERROR DeconstructAttestationElements(const ByteSpan & attestationElements, ByteSpan & certificationDeclaration,
ByteSpan & attestationNonce, uint32_t & timestamp, ByteSpan & firmwareInfo,
ByteSpan * vendorReservedArray, size_t & vendorReservedArraySize, uint16_t & vendorId,
uint16_t & profileNum);

/**
* @brief Take each component separately and form the Attestation Elements buffer.
*
* @param[in] certificationDeclaration Valid Certification Declaration data.
* @param[in] attestationNonce Attestation Nonce - 32 octets required.
* @param[in] timestamp Timestamp data in epoch time format.
* @param[in] firmwareInfo Optional Firmware Information data - Can be empty.
* @param[in] vendorReservedArray Array of Vendor Reserved entries.
* @param[in] vendorReservedArraySize Number of Vendor Reserved entries present in the array.
* @param[in] vendorId Vendor ID to be written to Vendor Reserved entries' Qualified Tags
* @param[in] profileNum Profile Number to be written to Vendor Reserved entries' Qualified Tags
* @param[out] attestationElements Buffer used to write all AttestationElements data, formed with all the data fields above.
* Provided buffer needs to be capable to handle all data fields + tags.
*/
CHIP_ERROR ConstructAttestationElements(const ByteSpan & certificationDeclaration, const ByteSpan & attestationNonce,
uint32_t timestamp, const ByteSpan & firmwareInfo, ByteSpan * vendorReservedArray,
size_t vendorReservedArraySize, uint16_t vendorId, uint16_t profileNum,
MutableByteSpan & attestationElements);

} // namespace Credentials
} // namespace chip
14 changes: 6 additions & 8 deletions src/credentials/DeviceAttestationVerifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,13 @@ enum class AttestationVerificationResult : uint16_t
kFirmwareInformationMismatch = 400,
kFirmwareInformationMissing = 401,

kCertificationDeclarationMissing = 500,
kCertificationDeclarationMissing = 500,
kAttestationSignatureInvalid = 501,
kAttestationElementsMalformed = 502,
kAttestationNonceMismatch = 503,
kAttestationSignatureInvalidFormat = 504,

kNonceMismatch = 600,

kInvalidSignatureFormat = 700,

kAttestationSignatureInvalid = 800,

kNoMemory = 900,
kNoMemory = 600,

kNotImplemented = 0xFFFFU,

Expand Down
25 changes: 18 additions & 7 deletions src/credentials/examples/DeviceAttestationVerifierExample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "DeviceAttestationVerifierExample.h"

#include <credentials/CHIPCert.h>
#include <credentials/DeviceAttestationConstructor.h>
#include <crypto/CHIPCryptoPAL.h>

#include <lib/core/CHIPError.h>
Expand Down Expand Up @@ -177,7 +178,7 @@ AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(c
P256ECDSASignature deviceSignature;
// SetLength will fail if signature doesn't fit
VerifyOrReturnError(deviceSignature.SetLength(attestationSignatureBuffer.size()) == CHIP_NO_ERROR,
AttestationVerificationResult::kInvalidSignatureFormat);
AttestationVerificationResult::kAttestationSignatureInvalidFormat);
memcpy(deviceSignature.Bytes(), attestationSignatureBuffer.data(), attestationSignatureBuffer.size());
VerifyOrReturnError(ValidateAttestationSignature(remoteManufacturerPubkey, attestationInfoBuffer, attestationChallengeBuffer,
deviceSignature) == CHIP_NO_ERROR,
Expand All @@ -198,14 +199,24 @@ AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(c
dacCertDerBuffer.data(), dacCertDerBuffer.size()) == CHIP_NO_ERROR,
AttestationVerificationResult::kDacSignatureInvalid);

// TODO: Re-enable this when Construct/DeconstructAttestationElements methods are introduced
#if 0
ReturnErrorOnFailure(
DeconstructAttestationElements(attestationElements, certDeclaration, attestationNonce, timestamp, firmwareInfo));
ByteSpan certificationDeclarationSpan;
ByteSpan attestationNonceSpan;
uint32_t timestampDeconstructed;
ByteSpan firmwareInfoSpan;
// TODO: refactor once final vendor-specific data tags is handled.
ByteSpan vendorReservedDeconstructed[2];
size_t vendorReservedDeconstructedSize = ArraySize(vendorReservedDeconstructed);
uint16_t vendorIdDeconstructed;
uint16_t profileNumDeconstructed;
VerifyOrReturnError(DeconstructAttestationElements(attestationInfoBuffer, certificationDeclarationSpan, attestationNonceSpan,
timestampDeconstructed, firmwareInfoSpan, vendorReservedDeconstructed,
vendorReservedDeconstructedSize, vendorIdDeconstructed,
profileNumDeconstructed) == CHIP_NO_ERROR,
AttestationVerificationResult::kAttestationElementsMalformed);

// Verify that Nonce matches with what we sent
VerifyOrReturnError(attestation_nonce.data_equal(attestationNonce), AttestationVerificationResult::kNonceMismatch);
#endif
VerifyOrReturnError(attestationNonceSpan.data_equal(attestationNonce),
AttestationVerificationResult::kAttestationNonceMismatch);

return AttestationVerificationResult::kSuccess;
}
Expand Down
1 change: 1 addition & 0 deletions src/credentials/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ chip_test_suite("tests") {
test_sources = [
"TestChipCert.cpp",
"TestChipOperationalCredentials.cpp",
"TestDeviceAttestationConstruction.cpp",
"TestDeviceAttestationCredentials.cpp",
]

Expand Down
Loading

0 comments on commit 1240433

Please sign in to comment.