From 2359744213552f5d66895c0e1485b3750ce9ad0f Mon Sep 17 00:00:00 2001 From: Vijay Selvaraj Date: Fri, 3 Dec 2021 10:34:55 -0500 Subject: [PATCH] Added time validity checking to Device Attestation Verifier (#12212) * 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 --- src/credentials/BUILD.gn | 5 + src/credentials/DeviceAttestationVerifier.cpp | 6 +- src/credentials/DeviceAttestationVerifier.h | 13 +-- .../DefaultDeviceAttestationVerifier.cpp | 40 +++++--- src/crypto/CHIPCryptoPAL.h | 30 ++++++ src/crypto/CHIPCryptoPALOpenSSL.cpp | 74 +++++++++++++++ src/crypto/CHIPCryptoPALmbedTLS.cpp | 94 +++++++++++++++++++ src/crypto/tests/BUILD.gn | 4 + src/crypto/tests/CHIPCryptoPALTest.cpp | 87 +++++++++++++++++ 9 files changed, 329 insertions(+), 24 deletions(-) diff --git a/src/credentials/BUILD.gn b/src/credentials/BUILD.gn index e0d5f418bae3b3..dea2d776e15ae2 100644 --- a/src/credentials/BUILD.gn +++ b/src/credentials/BUILD.gn @@ -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" @@ -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 = [ diff --git a/src/credentials/DeviceAttestationVerifier.cpp b/src/credentials/DeviceAttestationVerifier.cpp index 379b08aa7af640..e4cd29ca6fa6f9 100644 --- a/src/credentials/DeviceAttestationVerifier.cpp +++ b/src/credentials/DeviceAttestationVerifier.cpp @@ -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; } diff --git a/src/credentials/DeviceAttestationVerifier.h b/src/credentials/DeviceAttestationVerifier.h index 75ee5a0eb50d0e..292597a1fb25eb 100644 --- a/src/credentials/DeviceAttestationVerifier.h +++ b/src/credentials/DeviceAttestationVerifier.h @@ -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 diff --git a/src/credentials/examples/DefaultDeviceAttestationVerifier.cpp b/src/credentials/examples/DefaultDeviceAttestationVerifier.cpp index 23acd0179f59f7..bc26e558f945a3 100644 --- a/src/credentials/examples/DefaultDeviceAttestationVerifier.cpp +++ b/src/credentials/examples/DefaultDeviceAttestationVerifier.cpp @@ -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, @@ -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); @@ -214,9 +214,9 @@ 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); @@ -224,7 +224,7 @@ AttestationVerificationResult DefaultDACVerifier::VerifyAttestationInformation(c } 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 @@ -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 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) @@ -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); diff --git a/src/crypto/CHIPCryptoPAL.h b/src/crypto/CHIPCryptoPAL.h index 78a58be47eb604..8fce34addbc45e 100644 --- a/src/crypto/CHIPCryptoPAL.h +++ b/src/crypto/CHIPCryptoPAL.h @@ -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); /** diff --git a/src/crypto/CHIPCryptoPALOpenSSL.cpp b/src/crypto/CHIPCryptoPALOpenSSL.cpp index e20826b6f86c14..44352ba369b9bc 100644 --- a/src/crypto/CHIPCryptoPALOpenSSL.cpp +++ b/src/crypto/CHIPCryptoPALOpenSSL.cpp @@ -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(referenceCertificate.size())); + VerifyOrExit(x509ReferenceCertificate != nullptr, error = CHIP_ERROR_NO_MEMORY); + + x509toBeEvaluatedCertificate = d2i_X509(NULL, &pToBeEvaluatedCertificate, static_cast(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(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; diff --git a/src/crypto/CHIPCryptoPALmbedTLS.cpp b/src/crypto/CHIPCryptoPALmbedTLS.cpp index 5d9c047fd6d336..012113ec069e4b 100644 --- a/src/crypto/CHIPCryptoPALmbedTLS.cpp +++ b/src/crypto/CHIPCryptoPALmbedTLS.cpp @@ -1271,6 +1271,100 @@ CHIP_ERROR ValidateCertificateChain(const uint8_t * rootCertificate, size_t root return error; } +inline bool IsTimeGreaterThanEqual(const mbedtls_x509_time * const timeA, const mbedtls_x509_time * const timeB) +{ + return timeA->year > timeB->year || (timeA->year == timeB->year && timeA->mon > timeB->mon) || + (timeA->year == timeB->year && timeA->mon == timeB->mon && timeA->day > timeB->day) || + (timeA->year == timeB->year && timeA->mon == timeB->mon && timeA->day == timeB->day && timeA->hour > timeB->hour) || + (timeA->year == timeB->year && timeA->mon == timeB->mon && timeA->day == timeB->day && timeA->hour == timeB->hour && + timeA->min > timeB->min) || + (timeA->year == timeB->year && timeA->mon == timeB->mon && timeA->day == timeB->day && timeA->hour == timeB->hour && + timeA->min == timeB->min && timeA->sec >= timeB->sec); +} + +CHIP_ERROR IsCertificateValidAtIssuance(const ByteSpan & referenceCertificate, const ByteSpan & toBeEvaluatedCertificate) +{ +#if defined(MBEDTLS_X509_CRT_PARSE_C) + CHIP_ERROR error = CHIP_NO_ERROR; + mbedtls_x509_crt mbedReferenceCertificate; + mbedtls_x509_crt mbedToBeEvaluatedCertificate; + mbedtls_x509_time refNotBeforeTime; + mbedtls_x509_time tbeNotBeforeTime; + mbedtls_x509_time tbeNotAfterTime; + int result; + + VerifyOrReturnError(!referenceCertificate.empty() && !toBeEvaluatedCertificate.empty(), CHIP_ERROR_INVALID_ARGUMENT); + + mbedtls_x509_crt_init(&mbedReferenceCertificate); + mbedtls_x509_crt_init(&mbedToBeEvaluatedCertificate); + + result = mbedtls_x509_crt_parse(&mbedReferenceCertificate, Uint8::to_const_uchar(referenceCertificate.data()), + referenceCertificate.size()); + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + + result = mbedtls_x509_crt_parse(&mbedToBeEvaluatedCertificate, Uint8::to_const_uchar(toBeEvaluatedCertificate.data()), + toBeEvaluatedCertificate.size()); + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + + refNotBeforeTime = mbedReferenceCertificate.valid_from; + tbeNotBeforeTime = mbedToBeEvaluatedCertificate.valid_from; + tbeNotAfterTime = mbedToBeEvaluatedCertificate.valid_to; + + // TODO: Handle PAA/PAI re-issue and enable below time validation + // check if referenceCertificate is issued at or after tbeCertificate's notBefore timestamp + // VerifyOrExit(IsTimeGreaterThanEqual(&refNotBeforeTime, &tbeNotBeforeTime), error = CHIP_ERROR_CERT_EXPIRED); + + // check if referenceCertificate is issued at or before tbeCertificate's notAfter timestamp + // VerifyOrExit(IsTimeGreaterThanEqual(&tbeNotAfterTime, &refNotBeforeTime), error = CHIP_ERROR_CERT_EXPIRED); + +exit: + _log_mbedTLS_error(result); + mbedtls_x509_crt_free(&mbedReferenceCertificate); + mbedtls_x509_crt_free(&mbedToBeEvaluatedCertificate); + +#else + (void) referenceCertificate; + (void) toBeEvaluatedCertificate; + CHIP_ERROR error = CHIP_ERROR_NOT_IMPLEMENTED; +#endif // defined(MBEDTLS_X509_CRT_PARSE_C) + + return error; +} + +CHIP_ERROR IsCertificateValidAtCurrentTime(const ByteSpan & certificate) +{ +#if defined(MBEDTLS_X509_CRT_PARSE_C) + CHIP_ERROR error = CHIP_NO_ERROR; + mbedtls_x509_crt mbedCertificate; + int result; + + VerifyOrReturnError(!certificate.empty(), CHIP_ERROR_INVALID_ARGUMENT); + + mbedtls_x509_crt_init(&mbedCertificate); + + result = mbedtls_x509_crt_parse(&mbedCertificate, Uint8::to_const_uchar(certificate.data()), certificate.size()); + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + + // check if certificate's notBefore timestamp is earlier than or equal to current time. + result = mbedtls_x509_time_is_past(&mbedCertificate.valid_from); + VerifyOrExit(result == 1, error = CHIP_ERROR_CERT_EXPIRED); + + // check if certificate's notAfter timestamp is later than current time. + result = mbedtls_x509_time_is_future(&mbedCertificate.valid_to); + VerifyOrExit(result == 1, error = CHIP_ERROR_CERT_EXPIRED); + +exit: + _log_mbedTLS_error(result); + mbedtls_x509_crt_free(&mbedCertificate); + +#else + (void) certificate; + CHIP_ERROR error = CHIP_ERROR_NOT_IMPLEMENTED; +#endif // defined(MBEDTLS_X509_CRT_PARSE_C) + + return error; +} + CHIP_ERROR ExtractPubkeyFromX509Cert(const ByteSpan & certificate, Crypto::P256PublicKey & pubkey) { #if defined(MBEDTLS_X509_CRT_PARSE_C) diff --git a/src/crypto/tests/BUILD.gn b/src/crypto/tests/BUILD.gn index cec9162d32aa7d..364d0b916257be 100644 --- a/src/crypto/tests/BUILD.gn +++ b/src/crypto/tests/BUILD.gn @@ -48,6 +48,10 @@ chip_test_suite("tests") { sources += [ "CHIPCryptoPALTest.cpp" ] } + if (chip_device_platform == "esp32" || chip_device_platform == "nrfconnect") { + defines = [ "CURRENT_TIME_NOT_IMPLEMENTED=1" ] + } + cflags = [ "-Wconversion" ] public_deps = [ diff --git a/src/crypto/tests/CHIPCryptoPALTest.cpp b/src/crypto/tests/CHIPCryptoPALTest.cpp index dc3989bec15880..a65b314364e1da 100644 --- a/src/crypto/tests/CHIPCryptoPALTest.cpp +++ b/src/crypto/tests/CHIPCryptoPALTest.cpp @@ -1868,6 +1868,92 @@ static void TestX509_CertChainValidation(nlTestSuite * inSuite, void * inContext NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); } +static void TestX509_IssuingTimestampValidation(nlTestSuite * inSuite, void * inContext) +{ + using namespace TestCerts; + using namespace ASN1; + + HeapChecker heapChecker(inSuite); + CHIP_ERROR err = CHIP_NO_ERROR; + + /* + credentials/test/attestation/Chip-Test-DAC-FFF1-8000-000A-Cert.pem + -----BEGIN CERTIFICATE----- + MIIB6jCCAY+gAwIBAgIIBRpp5eeAND4wCgYIKoZIzj0EAwIwRjEYMBYGA1UEAwwP + TWF0dGVyIFRlc3QgUEFJMRQwEgYKKwYBBAGConwCAQwERkZGMTEUMBIGCisGAQQB + gqJ8AgIMBDgwMDAwIBcNMjEwNjI4MTQyMzQzWhgPOTk5OTEyMzEyMzU5NTlaMEsx + HTAbBgNVBAMMFE1hdHRlciBUZXN0IERBQyAwMDBBMRQwEgYKKwYBBAGConwCAQwE + RkZGMTEUMBIGCisGAQQBgqJ8AgIMBDgwMDAwWTATBgcqhkjOPQIBBggqhkjOPQMB + BwNCAAR6hFivu5vNFeGa3NJm9mycL2B8dHR6NfgPN+EYEz+A8XYBEyePkfFaoPf4 + eTIJT+aftyhoqB4ml5s2izO1VDEDo2AwXjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB + /wQEAwIHgDAdBgNVHQ4EFgQU1a2yuIOOyAc8R3LcfoeX/rsjs64wHwYDVR0jBBgw + FoAUhPUd/57M2ik1lEhSDoXxKS2j7dcwCgYIKoZIzj0EAwIDSQAwRgIhAPL+Fnlk + P0xbynYuijQV7VEwBvzQUtpQbWLYvVFeN70IAiEAvi20eqszdReOEkmgeSCgrG6q + OS8H8W2E/ctS268o19k= + -----END CERTIFICATE----- + */ + /* + Validity + Not Before: Jun 28 14:23:43 2021 GMT + Not After : Dec 31 23:59:59 9999 GMT + */ + constexpr uint8_t kDacCertificate[] = { + 0x30, 0x82, 0x01, 0xEA, 0x30, 0x82, 0x01, 0x8F, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x05, 0x1A, 0x69, 0xE5, 0xE7, + 0x80, 0x34, 0x3E, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x30, 0x46, 0x31, 0x18, 0x30, + 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x0F, 0x4D, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, + 0x50, 0x41, 0x49, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, 0x01, 0x0C, + 0x04, 0x46, 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, + 0x02, 0x0C, 0x04, 0x38, 0x30, 0x30, 0x30, 0x30, 0x20, 0x17, 0x0D, 0x32, 0x31, 0x30, 0x36, 0x32, 0x38, 0x31, 0x34, 0x32, + 0x33, 0x34, 0x33, 0x5A, 0x18, 0x0F, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, + 0x5A, 0x30, 0x4B, 0x31, 0x1D, 0x30, 0x1B, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x14, 0x4D, 0x61, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x44, 0x41, 0x43, 0x20, 0x30, 0x30, 0x30, 0x41, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0A, + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, 0x01, 0x0C, 0x04, 0x46, 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, + 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, 0x02, 0x0C, 0x04, 0x38, 0x30, 0x30, 0x30, 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, 0x7A, 0x84, 0x58, 0xAF, 0xBB, 0x9B, 0xCD, 0x15, 0xE1, 0x9A, 0xDC, 0xD2, 0x66, 0xF6, 0x6C, + 0x9C, 0x2F, 0x60, 0x7C, 0x74, 0x74, 0x7A, 0x35, 0xF8, 0x0F, 0x37, 0xE1, 0x18, 0x13, 0x3F, 0x80, 0xF1, 0x76, 0x01, 0x13, + 0x27, 0x8F, 0x91, 0xF1, 0x5A, 0xA0, 0xF7, 0xF8, 0x79, 0x32, 0x09, 0x4F, 0xE6, 0x9F, 0xB7, 0x28, 0x68, 0xA8, 0x1E, 0x26, + 0x97, 0x9B, 0x36, 0x8B, 0x33, 0xB5, 0x54, 0x31, 0x03, 0xA3, 0x60, 0x30, 0x5E, 0x30, 0x0C, 0x06, 0x03, 0x55, 0x1D, 0x13, + 0x01, 0x01, 0xFF, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x1D, 0x0F, 0x01, 0x01, 0xFF, 0x04, 0x04, 0x03, + 0x02, 0x07, 0x80, 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0xD5, 0xAD, 0xB2, 0xB8, 0x83, 0x8E, + 0xC8, 0x07, 0x3C, 0x47, 0x72, 0xDC, 0x7E, 0x87, 0x97, 0xFE, 0xBB, 0x23, 0xB3, 0xAE, 0x30, 0x1F, 0x06, 0x03, 0x55, 0x1D, + 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x84, 0xF5, 0x1D, 0xFF, 0x9E, 0xCC, 0xDA, 0x29, 0x35, 0x94, 0x48, 0x52, 0x0E, + 0x85, 0xF1, 0x29, 0x2D, 0xA3, 0xED, 0xD7, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x03, + 0x49, 0x00, 0x30, 0x46, 0x02, 0x21, 0x00, 0xF2, 0xFE, 0x16, 0x79, 0x64, 0x3F, 0x4C, 0x5B, 0xCA, 0x76, 0x2E, 0x8A, 0x34, + 0x15, 0xED, 0x51, 0x30, 0x06, 0xFC, 0xD0, 0x52, 0xDA, 0x50, 0x6D, 0x62, 0xD8, 0xBD, 0x51, 0x5E, 0x37, 0xBD, 0x08, 0x02, + 0x21, 0x00, 0xBE, 0x2D, 0xB4, 0x7A, 0xAB, 0x33, 0x75, 0x17, 0x8E, 0x12, 0x49, 0xA0, 0x79, 0x20, 0xA0, 0xAC, 0x6E, 0xAA, + 0x39, 0x2F, 0x07, 0xF1, 0x6D, 0x84, 0xFD, 0xCB, 0x52, 0xDB, 0xAF, 0x28, 0xD7, 0xD9 + }; + ByteSpan kDacCert(kDacCertificate); + + ByteSpan rootCert; + err = GetTestCert(TestCert::kRoot01, TestCertLoadFlags::kDERForm, rootCert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ByteSpan icaCert; + err = GetTestCert(TestCert::kICA01, TestCertLoadFlags::kDERForm, icaCert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ByteSpan leafCert; + err = GetTestCert(TestCert::kNode01_01, TestCertLoadFlags::kDERForm, leafCert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = IsCertificateValidAtIssuance(leafCert, icaCert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = IsCertificateValidAtIssuance(leafCert, rootCert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = IsCertificateValidAtIssuance(kDacCert, leafCert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + +#if !defined(CURRENT_TIME_NOT_IMPLEMENTED) + // test certificate validity (this one contains validity until year 9999 so it will not fail soon) + err = IsCertificateValidAtCurrentTime(kDacCert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); +#endif +} + static void TestSKID_x509Extraction(nlTestSuite * inSuite, void * inContext) { using namespace TestCerts; @@ -2111,6 +2197,7 @@ static const nlTest sTests[] = { NL_TEST_DEF("Test x509 Certificate Extraction from PKCS7", TestX509_PKCS7Extraction), #endif // CHIP_CRYPTO_OPENSSL NL_TEST_DEF("Test x509 Certificate Chain Validation", TestX509_CertChainValidation), + NL_TEST_DEF("Test x509 Certificate Timestamp Validation", TestX509_IssuingTimestampValidation), NL_TEST_DEF("Test Subject Key Id Extraction from x509 Certificate", TestSKID_x509Extraction), NL_TEST_DEF("Test Authority Key Id Extraction from x509 Certificate", TestAKID_x509Extraction), NL_TEST_DEF("Test Vendor ID Extraction from x509 Attestation Certificate", TestVID_x509Extraction),