Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added time validity checking to Device Attestation Verifier #12212

Merged
merged 6 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
38 changes: 23 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,31 @@ 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,
VerifyOrReturnError(IsCertificateValid(dacDerBuffer) == CHIP_NO_ERROR, AttestationVerificationResult::kDacExpired);

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 +297,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 timestamp does not satisfy the reference certificate's issuing timestamp.
vijs marked this conversation as resolved.
Show resolved Hide resolved
* - 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 IsCertificateValid(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 IsCertificateValid(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