Skip to content

Commit

Permalink
Added new method to validate NOCSR Attestation Signature (#13071)
Browse files Browse the repository at this point in the history
* Added new method to validate NOCSR Attestation Signature

* Reverted GenerateNOCChain method changes

* Addressed PR #13071 review comments
  • Loading branch information
vijs authored and pull[bot] committed Jul 18, 2023
1 parent 589c0d7 commit 75edc27
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 6 deletions.
15 changes: 15 additions & 0 deletions src/controller/CHIPDeviceController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,21 @@ CHIP_ERROR DeviceCommissioner::ProcessOpCSR(const ByteSpan & NOCSRElements, cons

ChipLogProgress(Controller, "Getting certificate chain for the device from the issuer");

DeviceAttestationVerifier * dacVerifier = GetDeviceAttestationVerifier();

P256PublicKey dacPubkey;
ReturnErrorOnFailure(ExtractPubkeyFromX509Cert(device->GetDAC(), dacPubkey));

// Retrieve attestation challenge
ByteSpan attestationChallenge = mSystemState->SessionMgr()
->GetSecureSession(device->GetSecureSession().Value())
->GetCryptoContext()
.GetAttestationChallenge();

// The operational CA should also verify this on its end during NOC generation, if end-to-end attestation is desired.
ReturnErrorOnFailure(dacVerifier->VerifyNodeOperationalCSRInformation(NOCSRElements, attestationChallenge, AttestationSignature,
dacPubkey, device->GetCSRNonce()));

mOperationalCredentialsDelegate->SetNodeIdForNextNOCRequest(device->GetDeviceId());

FabricInfo * fabric = mSystemState->Fabrics()->FindFabricWithIndex(mFabricIndex);
Expand Down
90 changes: 87 additions & 3 deletions src/credentials/DeviceAttestationConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,23 @@ namespace chip {
namespace Credentials {

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

enum OperationalCSRInfoId : uint32_t
{
kCsr = 1,
kCsrNonce = 2,
kVendorReserved1 = 3,
kVendorReserved2 = 4,
kVendorReserved3 = 5,
};

// utility to determine number of Vendor Reserved elements in a bytespan
CHIP_ERROR CountVendorReservedElementsInDA(const ByteSpan & attestationElements, size_t & numOfElements)
{
Expand Down Expand Up @@ -141,8 +150,6 @@ CHIP_ERROR DeconstructAttestationElements(const ByteSpan & attestationElements,
return CHIP_NO_ERROR;
}

// 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,
DeviceAttestationVendorReservedConstructor & vendorReserved,
Expand Down Expand Up @@ -214,6 +221,83 @@ CHIP_ERROR ConstructNOCSRElements(const ByteSpan & csr, const ByteSpan & csrNonc
return CHIP_NO_ERROR;
}

CHIP_ERROR DeconstructNOCSRElements(const ByteSpan & nocsrElements, ByteSpan & csr, ByteSpan & csrNonce,
ByteSpan & vendor_reserved1, ByteSpan & vendor_reserved2, ByteSpan & vendor_reserved3)
{
bool csrExists = false;
bool csrNonceExists = false;
bool gotFirstContextTag = false;
uint32_t lastContextTagId = 0;

TLV::ContiguousBufferTLVReader tlvReader;
TLV::TLVType containerType = TLV::kTLVType_Structure;

// empty out the optional items initially
vendor_reserved1 = vendor_reserved2 = vendor_reserved3 = ByteSpan();

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

CHIP_ERROR error;

// process context tags first (should be in sorted order)
while ((error = tlvReader.Next()) == CHIP_NO_ERROR)
{
TLV::Tag tag = tlvReader.GetTag();
if (!TLV::IsContextTag(tag))
{
break;
}

// Ensure tag-order and correct first expected tag
uint32_t contextTagId = TLV::TagNumFromTag(tag);
if (!gotFirstContextTag)
{
// First tag must always be CSR
VerifyOrReturnError(contextTagId == kCsr, CHIP_ERROR_UNEXPECTED_TLV_ELEMENT);
gotFirstContextTag = true;
}
else
{
// Subsequent tags must always be in order
VerifyOrReturnError(contextTagId > lastContextTagId, CHIP_ERROR_UNEXPECTED_TLV_ELEMENT);
}
lastContextTagId = contextTagId;

switch (contextTagId)
{
case kCsr:
ReturnErrorOnFailure(tlvReader.GetByteView(csr));
csrExists = true;
break;
case kCsrNonce:
ReturnErrorOnFailure(tlvReader.GetByteView(csrNonce));
csrNonceExists = true;
break;
case kVendorReserved1:
ReturnErrorOnFailure(tlvReader.Get(vendor_reserved1));
break;
case kVendorReserved2:
ReturnErrorOnFailure(tlvReader.Get(vendor_reserved2));
break;
case kVendorReserved3:
ReturnErrorOnFailure(tlvReader.Get(vendor_reserved3));
break;
default:
// unrecognized TLV element
return CHIP_ERROR_INVALID_TLV_ELEMENT;
}
}

VerifyOrReturnError(error == CHIP_NO_ERROR || error == CHIP_END_OF_TLV, error);

const bool allTagsNeededPresent = csrExists && csrNonceExists;
VerifyOrReturnError(allTagsNeededPresent, CHIP_ERROR_MISSING_TLV_ELEMENT);

return CHIP_NO_ERROR;
}

} // namespace Credentials

} // namespace chip
14 changes: 14 additions & 0 deletions src/credentials/DeviceAttestationConstructor.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,19 @@ CHIP_ERROR ConstructNOCSRElements(const ByteSpan & csr, const ByteSpan & csrNonc
const ByteSpan & vendor_reserved2, const ByteSpan & vendor_reserved3,
MutableByteSpan & nocsrElements);

/**
* @brief Take the NOCSR elements buffer and return each component seperately.
* All output data stays valid while nocsrElements buffer is valid.
*
* @param[in] nocsrElements ByteSpan containg source of NOCSR Elements data
* @param[out] csr Certificate Signing Request Body
* @param[out] csrNonce CSR Nonce
* @param[out] vendor_reserved1 Optional vendor_reserved1 blob, empty if omitted
* @param[out] vendor_reserved2 Optional vendor_reserved2 blob, empty if omitted
* @param[out] vendor_reserved3 Optional vendor_reserved3 blob, empty if omitted
*/
CHIP_ERROR DeconstructNOCSRElements(const ByteSpan & nocsrElements, ByteSpan & csr, ByteSpan & csrNonce,
ByteSpan & vendor_reserved1, ByteSpan & vendor_reserved2, ByteSpan & vendor_reserved3);

} // namespace Credentials
} // namespace chip
13 changes: 13 additions & 0 deletions src/credentials/DeviceAttestationVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ class UnimplementedDACVerifier : public DeviceAttestationVerifier
(void) deviceInfo;
return AttestationVerificationResult::kNotImplemented;
}

CHIP_ERROR VerifyNodeOperationalCSRInformation(const ByteSpan & nocsrElementsBuffer,
const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer,
const Crypto::P256PublicKey & dacPublicKey, const ByteSpan & csrNonce) override
{
(void) nocsrElementsBuffer;
(void) attestationChallengeBuffer;
(void) attestationSignatureBuffer;
(void) dacPublicKey;
(void) csrNonce;
return CHIP_ERROR_NOT_IMPLEMENTED;
}
};

// Default to avoid nullptr on getter and cleanly handle new products/clients before
Expand Down
20 changes: 17 additions & 3 deletions src/credentials/DeviceAttestationVerifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,24 @@ class DeviceAttestationVerifier

// TODO: Validate Firmware Information

/**
* @brief Verify an operational certificate signing request payload against the DAC's public key.
*
* @param[in] nocsrElementsBuffer Buffer containing CSR elements as per specifications section 11.22.5.6. NOCSR Elements.
* @param[in] attestationChallengeBuffer Buffer containing the attestation challenge from the secure session
* @param[in] attestationSignatureBuffer Buffer containing the signature portion of CSR Response
* @param[in] dacPublicKey Public Key from the DAC's certificate received from device.
* @param[in] csrNonce Buffer containing CSR nonce.
*/
virtual CHIP_ERROR VerifyNodeOperationalCSRInformation(const ByteSpan & nocsrElementsBuffer,
const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer,
const Crypto::P256PublicKey & dacPublicKey,
const ByteSpan & csrNonce) = 0;

protected:
CHIP_ERROR ValidateAttestationSignature(const chip::Crypto::P256PublicKey & pubkey, const ByteSpan & attestationElements,
const ByteSpan & attestationChallenge,
const chip::Crypto::P256ECDSASignature & signature);
CHIP_ERROR ValidateAttestationSignature(const Crypto::P256PublicKey & pubkey, const ByteSpan & attestationElements,
const ByteSpan & attestationChallenge, const Crypto::P256ECDSASignature & signature);
};

/**
Expand Down
39 changes: 39 additions & 0 deletions src/credentials/examples/DefaultDeviceAttestationVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
#include "DefaultDeviceAttestationVerifier.h"

#include <controller/OperationalCredentialsDelegate.h>
#include <credentials/CHIPCert.h>
#include <credentials/CertificationDeclaration.h>
#include <credentials/DeviceAttestationConstructor.h>
Expand Down Expand Up @@ -160,6 +161,11 @@ class DefaultDACVerifier : public DeviceAttestationVerifier
const ByteSpan & firmwareInfo,
const DeviceInfoForAttestation & deviceInfo) override;

CHIP_ERROR VerifyNodeOperationalCSRInformation(const ByteSpan & nocsrElementsBuffer,
const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer, const P256PublicKey & dacPublicKey,
const ByteSpan & csrNonce) override;

protected:
DefaultDACVerifier() {}

Expand Down Expand Up @@ -367,6 +373,39 @@ AttestationVerificationResult DefaultDACVerifier::ValidateCertificateDeclaration
return AttestationVerificationResult::kSuccess;
}

CHIP_ERROR DefaultDACVerifier::VerifyNodeOperationalCSRInformation(const ByteSpan & nocsrElementsBuffer,
const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer,
const P256PublicKey & dacPublicKey, const ByteSpan & csrNonce)
{
VerifyOrReturnError(!nocsrElementsBuffer.empty() && !attestationChallengeBuffer.empty() &&
!attestationSignatureBuffer.empty() && !csrNonce.empty(),
CHIP_ERROR_INVALID_ARGUMENT);

VerifyOrReturnError(csrNonce.size() == Controller::kOpCSRNonceLength, CHIP_ERROR_INVALID_ARGUMENT);

ByteSpan csrSpan;
ByteSpan csrNonceSpan;
ByteSpan vendorReserved1Span;
ByteSpan vendorReserved2Span;
ByteSpan vendorReserved3Span;
ReturnErrorOnFailure(DeconstructNOCSRElements(nocsrElementsBuffer, csrSpan, csrNonceSpan, vendorReserved1Span,
vendorReserved2Span, vendorReserved3Span));

// Verify that Nonce matches with what we sent
VerifyOrReturnError(csrNonceSpan.data_equal(csrNonce), CHIP_ERROR_INVALID_ARGUMENT);

// Validate overall attestation signature on attestation information
P256ECDSASignature signature;
// SetLength will fail if signature doesn't fit
ReturnErrorOnFailure(signature.SetLength(attestationSignatureBuffer.size()));
memcpy(signature.Bytes(), attestationSignatureBuffer.data(), attestationSignatureBuffer.size());

ReturnErrorOnFailure(ValidateAttestationSignature(dacPublicKey, nocsrElementsBuffer, attestationChallengeBuffer, signature));

return CHIP_NO_ERROR;
}

} // namespace

const AttestationTrustStore * GetTestAttestationTrustStore()
Expand Down
67 changes: 67 additions & 0 deletions src/credentials/tests/TestDeviceAttestationConstruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,72 @@ static void TestNocsrElements_Construction(nlTestSuite * inSuite, void * inConte
NL_TEST_ASSERT(inSuite, nocsrElementsSpan.data_equal(ByteSpan(kNoCsrElementsVector)));
}

static void TestNocsrElements_Deconstruction(nlTestSuite * inSuite, void * inContext)
{
static constexpr uint8_t kNocsrNonce[32] = {
0x81, 0x4a, 0x4d, 0x4c, 0x1c, 0x4a, 0x8e, 0xbb, 0xea, 0xdb, 0x0a, 0xe2, 0x82, 0xf9, 0x91, 0xeb,
0x13, 0xac, 0x5f, 0x9f, 0xce, 0x94, 0x30, 0x93, 0x19, 0xaa, 0x94, 0x09, 0x6c, 0x8c, 0xd4, 0xb8,
};

static constexpr uint8_t kExampleCsr[221] = {
0x30, 0x81, 0xda, 0x30, 0x81, 0x81, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04,
0x0a, 0x0c, 0x03, 0x43, 0x53, 0x41, 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, 0x5c, 0xa2, 0x79, 0xe3, 0x66,
0x82, 0xc2, 0xd4, 0x6c, 0xe7, 0xd4, 0xcf, 0x89, 0x67, 0x84, 0x67, 0x08, 0xb5, 0xb9, 0xf8, 0x5b, 0x9c, 0xda, 0xfd,
0x8c, 0xa8, 0x85, 0x26, 0x12, 0xcb, 0x0f, 0x0c, 0x7a, 0x71, 0x31, 0x4e, 0xc8, 0xdc, 0x9c, 0x96, 0x34, 0xdd, 0xee,
0xfe, 0xe9, 0xf6, 0x3f, 0x0e, 0x8b, 0xd7, 0xda, 0xcf, 0xc3, 0xb6, 0xa4, 0x53, 0x2a, 0xad, 0xd8, 0x9a, 0x96, 0x51,
0xcd, 0x6e, 0xa0, 0x11, 0x30, 0x0f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x0e, 0x31, 0x02,
0x30, 0x00, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45,
0x02, 0x20, 0x0e, 0x67, 0x5e, 0xe1, 0xb3, 0xbb, 0xfe, 0x15, 0x2a, 0x17, 0x4a, 0xf5, 0x35, 0xe2, 0x2d, 0x55, 0xce,
0x10, 0xc1, 0x50, 0xca, 0xc0, 0x1b, 0x31, 0x18, 0xde, 0x05, 0xe8, 0xfd, 0x9f, 0x10, 0x48, 0x02, 0x21, 0x00, 0xd8,
0x8c, 0x57, 0xcc, 0x6e, 0x74, 0xf0, 0xe5, 0x48, 0x8a, 0x26, 0x16, 0x7a, 0x07, 0xfd, 0x6d, 0xbe, 0xf1, 0xaa, 0xad,
0x72, 0x1c, 0x58, 0x0b, 0x6e, 0xae, 0x21, 0xbe, 0x5e, 0x6d, 0x0c, 0x72,
};

const uint8_t kVendorReserved1[23] = {
0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x6e, 0x64, 0x6f,
0x72, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x31,
};

const uint8_t kVendorReserved3[24] = {
0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72,
0x76, 0x65, 0x64, 0x33, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
};

const uint8_t kNoCsrElementsVector[314] = {
0x15, 0x30, 0x01, 0xdd, 0x30, 0x81, 0xda, 0x30, 0x81, 0x81, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x31, 0x0c, 0x30, 0x0a, 0x06,
0x03, 0x55, 0x04, 0x0a, 0x0c, 0x03, 0x43, 0x53, 0x41, 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, 0x5c, 0xa2, 0x79, 0xe3,
0x66, 0x82, 0xc2, 0xd4, 0x6c, 0xe7, 0xd4, 0xcf, 0x89, 0x67, 0x84, 0x67, 0x08, 0xb5, 0xb9, 0xf8, 0x5b, 0x9c, 0xda, 0xfd,
0x8c, 0xa8, 0x85, 0x26, 0x12, 0xcb, 0x0f, 0x0c, 0x7a, 0x71, 0x31, 0x4e, 0xc8, 0xdc, 0x9c, 0x96, 0x34, 0xdd, 0xee, 0xfe,
0xe9, 0xf6, 0x3f, 0x0e, 0x8b, 0xd7, 0xda, 0xcf, 0xc3, 0xb6, 0xa4, 0x53, 0x2a, 0xad, 0xd8, 0x9a, 0x96, 0x51, 0xcd, 0x6e,
0xa0, 0x11, 0x30, 0x0f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x0e, 0x31, 0x02, 0x30, 0x00, 0x30,
0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x20, 0x0e, 0x67,
0x5e, 0xe1, 0xb3, 0xbb, 0xfe, 0x15, 0x2a, 0x17, 0x4a, 0xf5, 0x35, 0xe2, 0x2d, 0x55, 0xce, 0x10, 0xc1, 0x50, 0xca, 0xc0,
0x1b, 0x31, 0x18, 0xde, 0x05, 0xe8, 0xfd, 0x9f, 0x10, 0x48, 0x02, 0x21, 0x00, 0xd8, 0x8c, 0x57, 0xcc, 0x6e, 0x74, 0xf0,
0xe5, 0x48, 0x8a, 0x26, 0x16, 0x7a, 0x07, 0xfd, 0x6d, 0xbe, 0xf1, 0xaa, 0xad, 0x72, 0x1c, 0x58, 0x0b, 0x6e, 0xae, 0x21,
0xbe, 0x5e, 0x6d, 0x0c, 0x72, 0x30, 0x02, 0x20, 0x81, 0x4a, 0x4d, 0x4c, 0x1c, 0x4a, 0x8e, 0xbb, 0xea, 0xdb, 0x0a, 0xe2,
0x82, 0xf9, 0x91, 0xeb, 0x13, 0xac, 0x5f, 0x9f, 0xce, 0x94, 0x30, 0x93, 0x19, 0xaa, 0x94, 0x09, 0x6c, 0x8c, 0xd4, 0xb8,
0x30, 0x03, 0x17, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x73,
0x65, 0x72, 0x76, 0x65, 0x64, 0x31, 0x30, 0x05, 0x18, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x65,
0x72, 0x76, 0x65, 0x64, 0x33, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x18,
};

ByteSpan csrSpan;
ByteSpan csrNonceSpan;
ByteSpan vendorReserved1Span;
ByteSpan vendorReserved2Span;
ByteSpan vendorReserved3Span;
CHIP_ERROR err = DeconstructNOCSRElements(ByteSpan(kNoCsrElementsVector), csrSpan, csrNonceSpan, vendorReserved1Span,
vendorReserved2Span, vendorReserved3Span);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, csrSpan.data_equal(ByteSpan(kExampleCsr)));
NL_TEST_ASSERT(inSuite, csrNonceSpan.data_equal(ByteSpan(kNocsrNonce)));
NL_TEST_ASSERT(inSuite, vendorReserved1Span.data_equal(ByteSpan(kVendorReserved1)));
NL_TEST_ASSERT(inSuite, vendorReserved2Span.empty());
NL_TEST_ASSERT(inSuite, vendorReserved3Span.data_equal(ByteSpan(kVendorReserved3)));
}

/**
* Test Suite. It lists all the test functions.
*/
Expand All @@ -580,6 +646,7 @@ static const nlTest sTests[] = {
NL_TEST_DEF("Test Device Attestation Elements Deconstruction - Corrupted/Out of Order TLV", TestAttestationElements_DeconstructionUnordered),
NL_TEST_DEF("Test Device Attestation Elements Deconstruction - No vendor reserved", TestAttestationElements_DeconstructionNoVendorReserved),
NL_TEST_DEF("Test Device NOCSR Elements Construction", TestNocsrElements_Construction),
NL_TEST_DEF("Test Device NOCSR Elements Deconstruction", TestNocsrElements_Deconstruction),
NL_TEST_SENTINEL()
};
// clang-format on
Expand Down
Loading

0 comments on commit 75edc27

Please sign in to comment.