Skip to content

Commit

Permalink
Added time validity checking to Device Attestation Verifier (#12212)
Browse files Browse the repository at this point in the history
* Added time validity checking to Device Attestation Verifier

* Added new methods to validate a certificate and implemented review comments

* Renamed IsCertificateValid to IsCertificateValidAtCurrentTime

* Removed "invalid issuing timestamp" test

* Disabled cert validation test in certain platforms
  • Loading branch information
vijs authored and pull[bot] committed Jan 11, 2024
1 parent 3e31017 commit 3270557
Show file tree
Hide file tree
Showing 9 changed files with 329 additions and 24 deletions.
5 changes: 5 additions & 0 deletions src/credentials/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import("//build_overrides/chip.gni")
import("//build_overrides/nlassert.gni")
import("${chip_root}/src/crypto/crypto.gni")
import("${chip_root}/src/platform/device.gni")

static_library("credentials") {
output_name = "libCredentials"
Expand Down Expand Up @@ -48,6 +49,10 @@ static_library("credentials") {
sources += [ "${chip_root}/examples/platform/nxp/se05x/DeviceAttestationSe05xCredsExample.cpp" ]
}

if (chip_device_platform == "esp32" || chip_device_platform == "nrfconnect") {
defines = [ "CURRENT_TIME_NOT_IMPLEMENTED=1" ]
}

cflags = [ "-Wconversion" ]

public_deps = [
Expand Down
6 changes: 3 additions & 3 deletions src/credentials/DeviceAttestationVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ class UnimplementedDACVerifier : public DeviceAttestationVerifier
AttestationVerificationResult VerifyAttestationInformation(const ByteSpan & attestationInfoBuffer,
const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer,
const ByteSpan & paiCertDerBuffer, const ByteSpan & dacCertDerBuffer,
const ByteSpan & paiDerBuffer, const ByteSpan & dacDerBuffer,
const ByteSpan & attestationNonce) override
{
(void) attestationInfoBuffer;
(void) attestationChallengeBuffer;
(void) attestationSignatureBuffer;
(void) paiCertDerBuffer;
(void) dacCertDerBuffer;
(void) paiDerBuffer;
(void) dacDerBuffer;
(void) attestationNonce;
return AttestationVerificationResult::kNotImplemented;
}
Expand Down
13 changes: 7 additions & 6 deletions src/credentials/DeviceAttestationVerifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,18 +196,19 @@ class DeviceAttestationVerifier
* @param[in] attestationInfoBuffer Buffer containing attestation information portion of Attestation Response (raw TLV)
* @param[in] attestationChallengeBuffer Buffer containing the attestation challenge from the secure session
* @param[in] attestationSignatureBuffer Buffer the signature portion of Attestation Response
* @param[in] paiCertDerBuffer Buffer containing the PAI certificate from device in DER format.
* @param[in] paiDerBuffer Buffer containing the PAI certificate from device in DER format.
* If length zero, there was no PAI certificate.
* @param[in] dacCertDerBuffer Buffer containing the DAC certificate from device in DER format.
* @param[in] dacDerBuffer Buffer containing the DAC certificate from device in DER format.
* @param[in] attestationNonce Buffer containing attestation nonce.
*
* @returns AttestationVerificationResult::kSuccess on success or another specific
* value from AttestationVerificationResult enum on failure.
*/
virtual AttestationVerificationResult
VerifyAttestationInformation(const ByteSpan & attestationInfoBuffer, const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer, const ByteSpan & paiCertDerBuffer,
const ByteSpan & dacCertDerBuffer, const ByteSpan & attestationNonce) = 0;
virtual AttestationVerificationResult VerifyAttestationInformation(const ByteSpan & attestationInfoBuffer,
const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer,
const ByteSpan & paiDerBuffer, const ByteSpan & dacDerBuffer,
const ByteSpan & attestationNonce) = 0;

/**
* @brief Verify a CMS Signed Data signature against the CSA certificate of Subject Key Identifier that matches
Expand Down
40 changes: 25 additions & 15 deletions src/credentials/examples/DefaultDeviceAttestationVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class DefaultDACVerifier : public DeviceAttestationVerifier
AttestationVerificationResult VerifyAttestationInformation(const ByteSpan & attestationInfoBuffer,
const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer,
const ByteSpan & paiCertDerBuffer, const ByteSpan & dacCertDerBuffer,
const ByteSpan & paiDerBuffer, const ByteSpan & dacDerBuffer,
const ByteSpan & attestationNonce) override;

AttestationVerificationResult ValidateCertificationDeclarationSignature(const ByteSpan & cmsEnvelopeBuffer,
Expand All @@ -199,12 +199,12 @@ class DefaultDACVerifier : public DeviceAttestationVerifier
AttestationVerificationResult DefaultDACVerifier::VerifyAttestationInformation(const ByteSpan & attestationInfoBuffer,
const ByteSpan & attestationChallengeBuffer,
const ByteSpan & attestationSignatureBuffer,
const ByteSpan & paiCertDerBuffer,
const ByteSpan & dacCertDerBuffer,
const ByteSpan & paiDerBuffer,
const ByteSpan & dacDerBuffer,
const ByteSpan & attestationNonce)
{
VerifyOrReturnError(!attestationInfoBuffer.empty() && !attestationChallengeBuffer.empty() &&
!attestationSignatureBuffer.empty() && !paiCertDerBuffer.empty() && !dacCertDerBuffer.empty() &&
!attestationSignatureBuffer.empty() && !paiDerBuffer.empty() && !dacDerBuffer.empty() &&
!attestationNonce.empty(),
AttestationVerificationResult::kInvalidArgument);

Expand All @@ -214,17 +214,17 @@ AttestationVerificationResult DefaultDACVerifier::VerifyAttestationInformation(c
uint16_t paiVid = VendorId::NotSpecified;
uint16_t dacVid = VendorId::NotSpecified;

VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, paiCertDerBuffer, paiVid) == CHIP_NO_ERROR,
VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, paiDerBuffer, paiVid) == CHIP_NO_ERROR,
AttestationVerificationResult::kPaiFormatInvalid);
VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, dacCertDerBuffer, dacVid) == CHIP_NO_ERROR,
VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, dacDerBuffer, dacVid) == CHIP_NO_ERROR,
AttestationVerificationResult::kDacFormatInvalid);

VerifyOrReturnError(paiVid == dacVid, AttestationVerificationResult::kDacVendorIdMismatch);
dacVendorId = static_cast<VendorId>(dacVid);
}

P256PublicKey remoteManufacturerPubkey;
VerifyOrReturnError(ExtractPubkeyFromX509Cert(dacCertDerBuffer, remoteManufacturerPubkey) == CHIP_NO_ERROR,
VerifyOrReturnError(ExtractPubkeyFromX509Cert(dacDerBuffer, remoteManufacturerPubkey) == CHIP_NO_ERROR,
AttestationVerificationResult::kDacFormatInvalid);

// Validate overall attestation signature on attestation information
Expand All @@ -239,23 +239,33 @@ AttestationVerificationResult DefaultDACVerifier::VerifyAttestationInformation(c

uint8_t akidBuf[Crypto::kAuthorityKeyIdentifierLength];
MutableByteSpan akid(akidBuf);
ExtractAKIDFromX509Cert(paiCertDerBuffer, akid);
ExtractAKIDFromX509Cert(paiDerBuffer, akid);

constexpr size_t paaCertAllocatedLen = kMaxDERCertLength;
chip::Platform::ScopedMemoryBuffer<uint8_t> paaCert;
VerifyOrReturnError(paaCert.Alloc(paaCertAllocatedLen), AttestationVerificationResult::kNoMemory);
MutableByteSpan paa(paaCert.Get(), paaCertAllocatedLen);
VerifyOrReturnError(mAttestationTrustStore->GetProductAttestationAuthorityCert(akid, paa) == CHIP_NO_ERROR,
MutableByteSpan paaDerBuffer(paaCert.Get(), paaCertAllocatedLen);
VerifyOrReturnError(mAttestationTrustStore->GetProductAttestationAuthorityCert(akid, paaDerBuffer) == CHIP_NO_ERROR,
AttestationVerificationResult::kPaaNotFound);

VerifyOrReturnError(ValidateCertificateChain(paa.data(), paa.size(), paiCertDerBuffer.data(), paiCertDerBuffer.size(),
dacCertDerBuffer.data(), dacCertDerBuffer.size()) == CHIP_NO_ERROR,
#if !defined(CURRENT_TIME_NOT_IMPLEMENTED)
VerifyOrReturnError(IsCertificateValidAtCurrentTime(dacDerBuffer) == CHIP_NO_ERROR, AttestationVerificationResult::kDacExpired);
#endif

VerifyOrReturnError(IsCertificateValidAtIssuance(dacDerBuffer, paiDerBuffer) == CHIP_NO_ERROR,
AttestationVerificationResult::kPaiExpired);

VerifyOrReturnError(IsCertificateValidAtIssuance(dacDerBuffer, paaDerBuffer) == CHIP_NO_ERROR,
AttestationVerificationResult::kPaaExpired);

VerifyOrReturnError(ValidateCertificateChain(paaDerBuffer.data(), paaDerBuffer.size(), paiDerBuffer.data(), paiDerBuffer.size(),
dacDerBuffer.data(), dacDerBuffer.size()) == CHIP_NO_ERROR,
AttestationVerificationResult::kDacSignatureInvalid);

// if PAA contains VID, see if matches with DAC's VID.
{
uint16_t paaVid = VendorId::NotSpecified;
CHIP_ERROR error = ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, paa, paaVid);
CHIP_ERROR error = ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, paaDerBuffer, paaVid);
VerifyOrReturnError(error == CHIP_NO_ERROR || error == CHIP_ERROR_KEY_NOT_FOUND,
AttestationVerificationResult::kPaaFormatInvalid);
if (error != CHIP_ERROR_KEY_NOT_FOUND)
Expand Down Expand Up @@ -289,12 +299,12 @@ AttestationVerificationResult DefaultDACVerifier::VerifyAttestationInformation(c
.dacVendorId = dacVendorId,
.paiVendorId = dacVendorId,
};
VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kProductId, dacCertDerBuffer, deviceInfo.dacProductId) ==
VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kProductId, dacDerBuffer, deviceInfo.dacProductId) ==
CHIP_NO_ERROR,
AttestationVerificationResult::kDacFormatInvalid);
// If PID is missing from PAI, the next method call will return CHIP_ERROR_KEY_NOT_FOUND.
// Valid return values are then CHIP_NO_ERROR or CHIP_ERROR_KEY_NOT_FOUND.
CHIP_ERROR error = ExtractDNAttributeFromX509Cert(MatterOid::kProductId, paiCertDerBuffer, deviceInfo.paiProductId);
CHIP_ERROR error = ExtractDNAttributeFromX509Cert(MatterOid::kProductId, paiDerBuffer, deviceInfo.paiProductId);
VerifyOrReturnError(error == CHIP_NO_ERROR || error == CHIP_ERROR_KEY_NOT_FOUND,
AttestationVerificationResult::kPaiFormatInvalid);
return ValidateCertificateDeclarationPayload(certificationDeclarationPayload, firmwareInfoSpan, deviceInfo);
Expand Down
30 changes: 30 additions & 0 deletions src/crypto/CHIPCryptoPAL.h
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,36 @@ CHIP_ERROR GetNumberOfCertsFromPKCS7(const char * pkcs7, uint32_t * n_certs);
CHIP_ERROR ValidateCertificateChain(const uint8_t * rootCertificate, size_t rootCertificateLen, const uint8_t * caCertificate,
size_t caCertificateLen, const uint8_t * leafCertificate, size_t leafCertificateLen);

/**
* @brief Validate timestamp of a certificate (toBeEvaluatedCertificate) in comparison with other certificate's
* (referenceCertificate) issuing timestamp.
*
* Errors are:
* - CHIP_ERROR_CERT_EXPIRED if the certificate timestamp does not satisfy the reference certificate's issuing timestamp.
* - CHIP_ERROR_INVALID_ARGUMENT when passing an invalid argument.
* - CHIP_ERROR_INTERNAL on any unexpected crypto or data conversion errors.
*
* @param referenceCertificate A DER Certificate ByteSpan used as the issuing timestamp reference.
* @param toBeEvaluatedCertificate A DER Certificate ByteSpan used to evaluate issuance against the referenceCertificate.
*
* @returns a CHIP_ERROR (see above) on failure or CHIP_NO_ERROR otherwise.
**/
CHIP_ERROR IsCertificateValidAtIssuance(const ByteSpan & referenceCertificate, const ByteSpan & toBeEvaluatedCertificate);

/**
* @brief Validate a certificate's validity date against current time.
*
* Errors are:
* - CHIP_ERROR_CERT_EXPIRED if the certificate has expired.
* - CHIP_ERROR_INVALID_ARGUMENT when passing an invalid argument.
* - CHIP_ERROR_INTERNAL on any unexpected crypto or data conversion errors.
*
* @param certificate A DER Certificate ByteSpan used as the validity reference to be checked against current time.
*
* @returns a CHIP_ERROR (see above) on failure or CHIP_NO_ERROR otherwise.
**/
CHIP_ERROR IsCertificateValidAtCurrentTime(const ByteSpan & certificate);

CHIP_ERROR ExtractPubkeyFromX509Cert(const ByteSpan & certificate, Crypto::P256PublicKey & pubkey);

/**
Expand Down
74 changes: 74 additions & 0 deletions src/crypto/CHIPCryptoPALOpenSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,80 @@ CHIP_ERROR ValidateCertificateChain(const uint8_t * rootCertificate, size_t root
return err;
}

CHIP_ERROR IsCertificateValidAtIssuance(const ByteSpan & referenceCertificate, const ByteSpan & toBeEvaluatedCertificate)
{
CHIP_ERROR error = CHIP_NO_ERROR;
X509 * x509ReferenceCertificate = nullptr;
X509 * x509toBeEvaluatedCertificate = nullptr;
const unsigned char * pReferenceCertificate = referenceCertificate.data();
const unsigned char * pToBeEvaluatedCertificate = toBeEvaluatedCertificate.data();
ASN1_TIME * refNotBeforeTime = nullptr;
ASN1_TIME * tbeNotBeforeTime = nullptr;
ASN1_TIME * tbeNotAfterTime = nullptr;
// int result = 0;

VerifyOrReturnError(!referenceCertificate.empty() && !toBeEvaluatedCertificate.empty(), CHIP_ERROR_INVALID_ARGUMENT);

x509ReferenceCertificate = d2i_X509(NULL, &pReferenceCertificate, static_cast<long>(referenceCertificate.size()));
VerifyOrExit(x509ReferenceCertificate != nullptr, error = CHIP_ERROR_NO_MEMORY);

x509toBeEvaluatedCertificate = d2i_X509(NULL, &pToBeEvaluatedCertificate, static_cast<long>(toBeEvaluatedCertificate.size()));
VerifyOrExit(x509toBeEvaluatedCertificate != nullptr, error = CHIP_ERROR_NO_MEMORY);

refNotBeforeTime = X509_get_notBefore(x509ReferenceCertificate);
tbeNotBeforeTime = X509_get_notBefore(x509toBeEvaluatedCertificate);
tbeNotAfterTime = X509_get_notAfter(x509toBeEvaluatedCertificate);
VerifyOrExit(refNotBeforeTime && tbeNotBeforeTime && tbeNotAfterTime, error = CHIP_ERROR_INTERNAL);

// TODO: Handle PAA/PAI re-issue and enable below time validations
// result = ASN1_TIME_compare(refNotBeforeTime, tbeNotBeforeTime);
// check if referenceCertificate is issued at or after tbeCertificate's notBefore timestamp
// VerifyOrExit(result >= 0, error = CHIP_ERROR_CERT_EXPIRED);

// result = ASN1_TIME_compare(refNotBeforeTime, tbeNotAfterTime);
// check if referenceCertificate is issued at or before tbeCertificate's notAfter timestamp
// VerifyOrExit(result <= 0, error = CHIP_ERROR_CERT_EXPIRED);

exit:
X509_free(x509ReferenceCertificate);
X509_free(x509toBeEvaluatedCertificate);

return error;
}

CHIP_ERROR IsCertificateValidAtCurrentTime(const ByteSpan & certificate)
{
CHIP_ERROR error = CHIP_NO_ERROR;
X509 * x509Certificate = nullptr;
const unsigned char * pCertificate = certificate.data();
ASN1_TIME * time = nullptr;
int result = 0;

VerifyOrReturnError(!certificate.empty(), CHIP_ERROR_INVALID_ARGUMENT);

x509Certificate = d2i_X509(NULL, &pCertificate, static_cast<long>(certificate.size()));
VerifyOrExit(x509Certificate != nullptr, error = CHIP_ERROR_NO_MEMORY);

time = X509_get_notBefore(x509Certificate);
VerifyOrExit(time, error = CHIP_ERROR_INTERNAL);

result = X509_cmp_current_time(time);
// check if certificate's notBefore timestamp is earlier than or equal to current time.
VerifyOrExit(result == -1, error = CHIP_ERROR_CERT_EXPIRED);

time = X509_get_notAfter(x509Certificate);
VerifyOrExit(time, error = CHIP_ERROR_INTERNAL);

result = X509_cmp_current_time(time);
// check if certificate's notAfter timestamp is later than current time.
VerifyOrExit(result == 1, error = CHIP_ERROR_CERT_EXPIRED);

exit:
X509_free(x509Certificate);

return error;
}

CHIP_ERROR ExtractPubkeyFromX509Cert(const ByteSpan & certificate, Crypto::P256PublicKey & pubkey)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Expand Down
Loading

0 comments on commit 3270557

Please sign in to comment.