diff --git a/credentials/development/cd-certs/Chip-Test-CD-Cert.der b/credentials/development/cd-certs/Chip-Test-CD-Cert.der new file mode 100644 index 00000000000000..6a7732980d7c27 Binary files /dev/null and b/credentials/development/cd-certs/Chip-Test-CD-Cert.der differ diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp index 89dd1d536e4550..4baf3e15c8d07d 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.cpp +++ b/examples/chip-tool/commands/common/CHIPCommand.cpp @@ -36,27 +36,47 @@ std::set CHIPCommand::sDeferredCleanups; using DeviceControllerFactory = chip::Controller::DeviceControllerFactory; -constexpr chip::FabricId kIdentityNullFabricId = chip::kUndefinedFabricId; -constexpr chip::FabricId kIdentityAlphaFabricId = 1; -constexpr chip::FabricId kIdentityBetaFabricId = 2; -constexpr chip::FabricId kIdentityGammaFabricId = 3; -constexpr chip::FabricId kIdentityOtherFabricId = 4; -constexpr const char * kTrustStorePathVariable = "CHIPTOOL_PAA_TRUST_STORE_PATH"; - -const chip::Credentials::AttestationTrustStore * CHIPCommand::sPaaTrustStore = nullptr; +constexpr chip::FabricId kIdentityNullFabricId = chip::kUndefinedFabricId; +constexpr chip::FabricId kIdentityAlphaFabricId = 1; +constexpr chip::FabricId kIdentityBetaFabricId = 2; +constexpr chip::FabricId kIdentityGammaFabricId = 3; +constexpr chip::FabricId kIdentityOtherFabricId = 4; +constexpr const char * kPAATrustStorePathVariable = "CHIPTOOL_PAA_TRUST_STORE_PATH"; +constexpr const char * kCDTrustStorePathVariable = "CHIPTOOL_CD_TRUST_STORE_PATH"; + +const chip::Credentials::AttestationTrustStore * CHIPCommand::sTrustStore = nullptr; chip::Credentials::GroupDataProviderImpl CHIPCommand::sGroupDataProvider{ kMaxGroupsPerFabric, kMaxGroupKeysPerFabric }; namespace { -const chip::Credentials::AttestationTrustStore * GetTestFileAttestationTrustStore(const char * paaTrustStorePath) +const CHIP_ERROR GetAttestationTrustStore(const char * paaTrustStorePath, + const chip::Credentials::AttestationTrustStore ** trustStore) { + if (paaTrustStorePath == nullptr) + { + paaTrustStorePath = getenv(kPAATrustStorePathVariable); + } + + if (paaTrustStorePath == nullptr) + { + *trustStore = chip::Credentials::GetTestAttestationTrustStore(); + return CHIP_NO_ERROR; + } + static chip::Credentials::FileAttestationTrustStore attestationTrustStore{ paaTrustStorePath }; - if (attestationTrustStore.IsInitialized()) + if (paaTrustStorePath != nullptr && attestationTrustStore.paaCount() == 0) { - return &attestationTrustStore; + ChipLogError(chipTool, "No PAAs found in path: %s", paaTrustStorePath); + ChipLogError(chipTool, + "Please specify a valid path containing trusted PAA certificates using " + "the argument [--paa-trust-store-path paa/file/path] " + "or environment variable [%s=paa/file/path]", + kPAATrustStorePathVariable); + return CHIP_ERROR_INVALID_ARGUMENT; } - return nullptr; + *trustStore = &attestationTrustStore; + return CHIP_NO_ERROR; } } // namespace @@ -103,29 +123,33 @@ CHIP_ERROR CHIPCommand::MaybeSetUpStack() factoryInitParams.listenPort = port; ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().Init(factoryInitParams)); - if (!mPaaTrustStorePath.HasValue()) + ReturnErrorOnFailure(GetAttestationTrustStore(mPaaTrustStorePath.ValueOr(nullptr), &sTrustStore)); + + ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityNull, kIdentityNullFabricId)); + + // After initializing first commissioner, add the additional CD certs once { - char * const trust_store_path = getenv(kTrustStorePathVariable); - if (trust_store_path != nullptr) + const char * cdTrustStorePath = mCDTrustStorePath.ValueOr(nullptr); + if (cdTrustStorePath == nullptr) { - mPaaTrustStorePath.SetValue(trust_store_path); + cdTrustStorePath = getenv(kCDTrustStorePathVariable); } - } - sPaaTrustStore = mPaaTrustStorePath.HasValue() ? GetTestFileAttestationTrustStore(mPaaTrustStorePath.Value()) - : chip::Credentials::GetTestAttestationTrustStore(); - ; - if (mPaaTrustStorePath.HasValue() && sPaaTrustStore == nullptr) - { - ChipLogError(chipTool, "No PAAs found in path: %s", mPaaTrustStorePath.Value()); - ChipLogError(chipTool, - "Please specify a valid path containing trusted PAA certificates using" - "the argument [--paa-trust-store-path paa/file/path]" - "or environment variable [%s=paa/file/path]", - kTrustStorePathVariable); - return CHIP_ERROR_INVALID_ARGUMENT; - } - ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityNull, kIdentityNullFabricId)); + auto additionalCdCerts = chip::Credentials::LoadAllX509DerCerts(cdTrustStorePath); + if (cdTrustStorePath != nullptr && additionalCdCerts.size() == 0) + { + ChipLogError(chipTool, "Warning: no CD signing certs found in path: %s, only defaults will be used", cdTrustStorePath); + ChipLogError(chipTool, + "Please specify a path containing trusted CD verifying key certificates using " + "the argument [--cd-trust-store-path cd/file/path] " + "or environment variable [%s=cd/file/path]", + kCDTrustStorePathVariable); + } + ReturnErrorOnFailure(mCredIssuerCmds->AddAdditionalCDVerifyingCerts(additionalCdCerts)); + } + bool allowTestCdSigningKey = !mOnlyAllowTrustedCdKeys.ValueOr(false); + mCredIssuerCmds->SetCredentialIssuerOption(CredentialIssuerCommands::CredentialIssuerOptions::kAllowTestCdSigningKey, + allowTestCdSigningKey); return CHIP_NO_ERROR; } @@ -343,7 +367,7 @@ CHIP_ERROR CHIPCommand::InitializeCommissioner(std::string key, chip::FabricId f std::unique_ptr commissioner = std::make_unique(); chip::Controller::SetupParams commissionerParams; - ReturnLogErrorOnFailure(mCredIssuerCmds->SetupDeviceAttestation(commissionerParams, sPaaTrustStore)); + ReturnLogErrorOnFailure(mCredIssuerCmds->SetupDeviceAttestation(commissionerParams, sTrustStore)); VerifyOrReturnError(noc.Alloc(chip::Controller::kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY); VerifyOrReturnError(icac.Alloc(chip::Controller::kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY); diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h index 7dd36a7c7d6214..35f562ce2ce7e8 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.h +++ b/examples/chip-tool/commands/common/CHIPCommand.h @@ -65,6 +65,9 @@ class CHIPCommand : public Command AddArgument("paa-trust-store-path", &mPaaTrustStorePath, "Path to directory holding PAA certificate information. Can be absolute or relative to the current working " "directory."); + AddArgument("cd-trust-store-path", &mCDTrustStorePath, + "Path to directory holding CD certificate information. Can be absolute or relative to the current working " + "directory."); AddArgument("commissioner-name", &mCommissionerName, "Name of fabric to use. Valid values are \"alpha\", \"beta\", \"gamma\", and integers greater than or equal to " "4. The default if not specified is \"alpha\"."); @@ -73,6 +76,9 @@ class CHIPCommand : public Command AddArgument("use-max-sized-certs", 0, 1, &mUseMaxSizedCerts, "Maximize the size of operational certificates. If not provided or 0 (\"false\"), normally sized operational " "certificates are generated."); + AddArgument("only-allow-trusted-cd-keys", 0, 1, &mOnlyAllowTrustedCdKeys, + "Only allow trusted CD verifying keys (disallow test keys). If not provided or 0 (\"false\"), untrusted CD " + "verifying keys are allowed. If 1 (\"true\"), test keys are disallowed."); #if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED AddArgument("trace_file", &mTraceFile); AddArgument("trace_log", 0, 1, &mTraceLog); @@ -156,11 +162,13 @@ class CHIPCommand : public Command chip::Optional mCommissionerNodeId; chip::Optional mBleAdapterId; chip::Optional mPaaTrustStorePath; + chip::Optional mCDTrustStorePath; chip::Optional mUseMaxSizedCerts; + chip::Optional mOnlyAllowTrustedCdKeys; // Cached trust store so commands other than the original startup command // can spin up commissioners as needed. - static const chip::Credentials::AttestationTrustStore * sPaaTrustStore; + static const chip::Credentials::AttestationTrustStore * sTrustStore; static void RunQueuedCommand(intptr_t commandArg); diff --git a/examples/chip-tool/commands/common/CredentialIssuerCommands.h b/examples/chip-tool/commands/common/CredentialIssuerCommands.h index cc04863ee2f8b6..2a5df5a42abe63 100644 --- a/examples/chip-tool/commands/common/CredentialIssuerCommands.h +++ b/examples/chip-tool/commands/common/CredentialIssuerCommands.h @@ -23,6 +23,7 @@ #include #include #include +#include class CredentialIssuerCommands { @@ -54,6 +55,16 @@ class CredentialIssuerCommands virtual CHIP_ERROR SetupDeviceAttestation(chip::Controller::SetupParams & setupParams, const chip::Credentials::AttestationTrustStore * trustStore) = 0; + /** + * @brief Add a list of additional non-default CD verifying keys (by certificate) + * + * Must be called AFTER SetupDeviceAttestation. + * + * @param additionalCdCerts - vector of X.509 DER verifying cert bodies + * @return CHIP_NO_ERROR on succes, another CHIP_ERROR on internal failures. + */ + virtual CHIP_ERROR AddAdditionalCDVerifyingCerts(const std::vector> & additionalCdCerts) = 0; + virtual chip::Controller::OperationalCredentialsDelegate * GetCredentialIssuer() = 0; /** @@ -79,6 +90,7 @@ class CredentialIssuerCommands enum CredentialIssuerOptions : uint8_t { kMaximizeCertificateSizes = 0, // If set, certificate chains will be maximized for testing via padding + kAllowTestCdSigningKey = 1, // If set, allow development/test SDK CD verifying key to be used }; virtual void SetCredentialIssuerOption(CredentialIssuerOptions option, bool isEnabled) diff --git a/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h b/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h index 40a2871b19437b..da4716e957e921 100644 --- a/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h +++ b/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h @@ -37,7 +37,9 @@ class ExampleCredentialIssuerCommands : public CredentialIssuerCommands { chip::Credentials::SetDeviceAttestationCredentialsProvider(chip::Credentials::Examples::GetExampleDACProvider()); - setupParams.deviceAttestationVerifier = chip::Credentials::GetDefaultDACVerifier(trustStore); + mDacVerifier = chip::Credentials::GetDefaultDACVerifier(trustStore); + setupParams.deviceAttestationVerifier = mDacVerifier; + mDacVerifier->EnableCdTestKeySupport(mAllowTestCdSigningKey); return CHIP_NO_ERROR; } @@ -49,6 +51,20 @@ class ExampleCredentialIssuerCommands : public CredentialIssuerCommands return mOpCredsIssuer.GenerateNOCChainAfterValidation(nodeId, fabricId, cats, keypair.Pubkey(), rcac, icac, noc); } + CHIP_ERROR AddAdditionalCDVerifyingCerts(const std::vector> & additionalCdCerts) override + { + VerifyOrReturnError(mDacVerifier != nullptr, CHIP_ERROR_INCORRECT_STATE); + + for (const auto & cert : additionalCdCerts) + { + auto cdTrustStore = mDacVerifier->GetCertificationDeclarationTrustStore(); + VerifyOrReturnError(cdTrustStore != nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorOnFailure(cdTrustStore->AddTrustedKey(chip::ByteSpan(cert.data(), cert.size()))); + } + + return CHIP_NO_ERROR; + } + void SetCredentialIssuerOption(CredentialIssuerOptions option, bool isEnabled) override { switch (option) @@ -57,6 +73,13 @@ class ExampleCredentialIssuerCommands : public CredentialIssuerCommands mUsesMaxSizedCerts = isEnabled; mOpCredsIssuer.SetMaximallyLargeCertsUsed(mUsesMaxSizedCerts); break; + case CredentialIssuerOptions::kAllowTestCdSigningKey: + mAllowTestCdSigningKey = isEnabled; + if (mDacVerifier != nullptr) + { + mDacVerifier->EnableCdTestKeySupport(isEnabled); + } + default: break; } @@ -68,6 +91,8 @@ class ExampleCredentialIssuerCommands : public CredentialIssuerCommands { case CredentialIssuerOptions::kMaximizeCertificateSizes: return mUsesMaxSizedCerts; + case CredentialIssuerOptions::kAllowTestCdSigningKey: + return mAllowTestCdSigningKey; default: return false; } @@ -75,7 +100,10 @@ class ExampleCredentialIssuerCommands : public CredentialIssuerCommands protected: bool mUsesMaxSizedCerts = false; + // Starts true for legacy purposes + bool mAllowTestCdSigningKey = true; private: chip::Controller::ExampleOperationalCredentialsIssuer mOpCredsIssuer; + chip::Credentials::DeviceAttestationVerifier * mDacVerifier; }; diff --git a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp index f60d4b2d70d325..7fc99aa38d79ad 100644 --- a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp +++ b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,62 @@ static const ByteSpan kTestPaaRoots[] = { TestCerts::sTestCert_PAA_NoVID_Cert, }; +// Test CD Signing Key from `credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem` +// used to verify any in-SDK development CDs. The associated keypair to do actual signing is in +// `credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem`. +// +// -----BEGIN CERTIFICATE----- +// MIIBszCCAVqgAwIBAgIIRdrzneR6oI8wCgYIKoZIzj0EAwIwKzEpMCcGA1UEAwwg +// TWF0dGVyIFRlc3QgQ0QgU2lnbmluZyBBdXRob3JpdHkwIBcNMjEwNjI4MTQyMzQz +// WhgPOTk5OTEyMzEyMzU5NTlaMCsxKTAnBgNVBAMMIE1hdHRlciBUZXN0IENEIFNp +// Z25pbmcgQXV0aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPDmJIkUr +// VcrzicJb0bykZWlSzLkOiGkkmthHRlMBTL+V1oeWXgNrUhxRA35rjO3vyh60QEZp +// T6CIgu7WUZ3suqNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8BAf8EBAMC +// AQYwHQYDVR0OBBYEFGL6gjNZrPqplj4c+hQK3fUE83FgMB8GA1UdIwQYMBaAFGL6 +// gjNZrPqplj4c+hQK3fUE83FgMAoGCCqGSM49BAMCA0cAMEQCICxUXOTkV9im8NnZ +// u+vW7OHd/n+MbZps83UyH8b6xxOEAiBUB3jodDlyUn7t669YaGIgtUB48s1OYqdq +// 58u5L/VMiw== +// -----END CERTIFICATE----- +// +constexpr uint8_t gTestCdPubkeyBytes[65] = { 0x04, 0x3c, 0x39, 0x89, 0x22, 0x45, 0x2b, 0x55, 0xca, 0xf3, 0x89, 0xc2, 0x5b, + 0xd1, 0xbc, 0xa4, 0x65, 0x69, 0x52, 0xcc, 0xb9, 0x0e, 0x88, 0x69, 0x24, 0x9a, + 0xd8, 0x47, 0x46, 0x53, 0x01, 0x4c, 0xbf, 0x95, 0xd6, 0x87, 0x96, 0x5e, 0x03, + 0x6b, 0x52, 0x1c, 0x51, 0x03, 0x7e, 0x6b, 0x8c, 0xed, 0xef, 0xca, 0x1e, 0xb4, + 0x40, 0x46, 0x69, 0x4f, 0xa0, 0x88, 0x82, 0xee, 0xd6, 0x51, 0x9d, 0xec, 0xba }; + +constexpr uint8_t gTestCdPubkeyKid[20] = { 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, 0x3e, + 0x1c, 0xfa, 0x14, 0x0a, 0xdd, 0xf5, 0x04, 0xf3, 0x71, 0x60 }; + +// Official CD "Signing Key 001" +// +// -----BEGIN CERTIFICATE----- +// MIICCDCCAa2gAwIBAgIHY3NhY2RzMTAKBggqhkjOPQQDAjBSMQwwCgYDVQQKDAND +// U0ExLDAqBgNVBAMMI01hdHRlciBDZXJ0aWZpY2F0aW9uIGFuZCBUZXN0aW5nIENB +// MRQwEgYKKwYBBAGConwCAQwEQzVBMDAgFw0yMjA4MTExOTMxMTVaGA8yMDcyMDcy +// OTE5MzExNVowWDEMMAoGA1UECgwDQ1NBMTIwMAYDVQQDDClDZXJ0aWZpY2F0aW9u +// IERlY2xhcmF0aW9uIFNpZ25pbmcgS2V5IDAwMTEUMBIGCisGAQQBgqJ8AgEMBEM1 +// QTAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARbW8Ou1rqjg/3Pm51ac/rqfmXr +// WSfBxcArHPpLi9trm36yUlE/I/IqWDOdyK24gEYKySHTdte5cMUMO+bm0jbwo2Yw +// ZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQU +// g/rXgegtAYtPFPChx/aEAYzF0Z8wHwYDVR0jBBgwFoAUl+Rp0MUEFMJvxwH3fpR3 +// OQmN9qUwCgYIKoZIzj0EAwIDSQAwRgIhAIbSu8KoWTj5792UxtJ/uSgQXVTLRRsm +// 09ys2m37JxDvAiEA8WMKDbRbwOtkabIyqwDgmiR3KwkyYwaqN4GPsRKfxwQ= +// -----END CERTIFICATE----- +// +constexpr uint8_t gCdSigningKey001PubkeyBytes[65] = { + 0x04, 0x5b, 0x5b, 0xc3, 0xae, 0xd6, 0xba, 0xa3, 0x83, 0xfd, 0xcf, 0x9b, 0x9d, 0x5a, 0x73, 0xfa, 0xea, + 0x7e, 0x65, 0xeb, 0x59, 0x27, 0xc1, 0xc5, 0xc0, 0x2b, 0x1c, 0xfa, 0x4b, 0x8b, 0xdb, 0x6b, 0x9b, 0x7e, + 0xb2, 0x52, 0x51, 0x3f, 0x23, 0xf2, 0x2a, 0x58, 0x33, 0x9d, 0xc8, 0xad, 0xb8, 0x80, 0x46, 0x0a, 0xc9, + 0x21, 0xd3, 0x76, 0xd7, 0xb9, 0x70, 0xc5, 0x0c, 0x3b, 0xe6, 0xe6, 0xd2, 0x36, 0xf0 +}; + +constexpr uint8_t gCdSigningKey001Kid[20] = { 0x83, 0xfa, 0xd7, 0x81, 0xe8, 0x2d, 0x01, 0x8b, 0x4f, 0x14, + 0xf0, 0xa1, 0xc7, 0xf6, 0x84, 0x01, 0x8c, 0xc5, 0xd1, 0x9f }; + +std::array gCdKids = { ByteSpan{ gTestCdPubkeyKid }, ByteSpan{ gCdSigningKey001Kid } }; +std::array gCdPubkeys = { Crypto::P256PublicKey{ gTestCdPubkeyBytes }, + Crypto::P256PublicKey{ gCdSigningKey001PubkeyBytes } }; + const ArrayAttestationTrustStore kTestAttestationTrustStore{ &kTestPaaRoots[0], ArraySize(kTestPaaRoots) }; AttestationVerificationResult MapError(CertificateChainValidationResult certificateChainValidationResult) @@ -87,64 +144,6 @@ AttestationVerificationResult MapError(CertificateChainValidationResult certific return AttestationVerificationResult::kInternalError; } } - -/** - * @brief Look-up of well-known keys used for CD signing by CSA. - * - * Current version uses only test key/cert provided in spec. - */ -CHIP_ERROR GetCertificationDeclarationCertificate(const ByteSpan & skid, MutableByteSpan & outCertificate) -{ - struct CertChainLookupTable - { - const uint8_t mCertificate[kMax_x509_Certificate_Length]; - const uint8_t mSKID[Crypto::kSubjectKeyIdentifierLength]; - }; - - static CertChainLookupTable - sCertChainLookupTable[] = { - { { 0x30, 0x82, 0x01, 0xb3, 0x30, 0x82, 0x01, 0x5a, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x45, 0xda, 0xf3, 0x9d, - 0xe4, 0x7a, 0xa0, 0x8f, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x2b, 0x31, - 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x20, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x54, 0x65, - 0x73, 0x74, 0x20, 0x43, 0x44, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 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, 0x2b, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x20, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x44, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 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, 0x3c, 0x39, 0x89, 0x22, - 0x45, 0x2b, 0x55, 0xca, 0xf3, 0x89, 0xc2, 0x5b, 0xd1, 0xbc, 0xa4, 0x65, 0x69, 0x52, 0xcc, 0xb9, 0x0e, 0x88, 0x69, - 0x24, 0x9a, 0xd8, 0x47, 0x46, 0x53, 0x01, 0x4c, 0xbf, 0x95, 0xd6, 0x87, 0x96, 0x5e, 0x03, 0x6b, 0x52, 0x1c, 0x51, - 0x03, 0x7e, 0x6b, 0x8c, 0xed, 0xef, 0xca, 0x1e, 0xb4, 0x40, 0x46, 0x69, 0x4f, 0xa0, 0x88, 0x82, 0xee, 0xd6, 0x51, - 0x9d, 0xec, 0xba, 0xa3, 0x66, 0x30, 0x64, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, - 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x62, 0xfa, 0x82, - 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, 0x3e, 0x1c, 0xfa, 0x14, 0x0a, 0xdd, 0xf5, 0x04, 0xf3, 0x71, 0x60, 0x30, 0x1f, - 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, - 0x96, 0x3e, 0x1c, 0xfa, 0x14, 0x0a, 0xdd, 0xf5, 0x04, 0xf3, 0x71, 0x60, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, - 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x47, 0x00, 0x30, 0x44, 0x02, 0x20, 0x2c, 0x54, 0x5c, 0xe4, 0xe4, 0x57, 0xd8, - 0xa6, 0xf0, 0xd9, 0xd9, 0xbb, 0xeb, 0xd6, 0xec, 0xe1, 0xdd, 0xfe, 0x7f, 0x8c, 0x6d, 0x9a, 0x6c, 0xf3, 0x75, 0x32, - 0x1f, 0xc6, 0xfa, 0xc7, 0x13, 0x84, 0x02, 0x20, 0x54, 0x07, 0x78, 0xe8, 0x74, 0x39, 0x72, 0x52, 0x7e, 0xed, 0xeb, - 0xaf, 0x58, 0x68, 0x62, 0x20, 0xb5, 0x40, 0x78, 0xf2, 0xcd, 0x4e, 0x62, 0xa7, 0x6a, 0xe7, 0xcb, 0xb9, 0x2f, 0xf5, - 0x4c, 0x8b }, - { 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, 0x3e, - 0x1c, 0xfa, 0x14, 0x0a, 0xdd, 0xf5, 0x04, 0xf3, 0x71, 0x60 } } - }; - - size_t certChainLookupTableIdx; - for (certChainLookupTableIdx = 0; certChainLookupTableIdx < ArraySize(sCertChainLookupTable); ++certChainLookupTableIdx) - { - if (skid.data_equal(ByteSpan(sCertChainLookupTable[certChainLookupTableIdx].mSKID))) - { - break; - } - } - - VerifyOrReturnError(certChainLookupTableIdx < ArraySize(sCertChainLookupTable), CHIP_ERROR_INVALID_ARGUMENT); - - return CopySpanToMutableSpan(ByteSpan{ sCertChainLookupTable[certChainLookupTableIdx].mCertificate }, outCertificate); -} - } // namespace void DefaultDACVerifier::VerifyAttestationInformation(const DeviceAttestationVerifier::AttestationInfo & info, @@ -166,6 +165,9 @@ void DefaultDACVerifier::VerifyAttestationInformation(const DeviceAttestationVer VerifyOrExit(info.attestationElementsBuffer.size() <= kMaxResponseLength, attestationError = AttestationVerificationResult::kInvalidArgument); + // Ensure PAI is present + VerifyOrExit(!info.paiDerBuffer.empty(), attestationError = AttestationVerificationResult::kPaiMissing); + // match DAC and PAI VIDs { VerifyOrExit(ExtractVIDPIDFromX509Cert(info.dacDerBuffer, dacVidPid) == CHIP_NO_ERROR, @@ -203,6 +205,7 @@ void DefaultDACVerifier::VerifyAttestationInformation(const DeviceAttestationVer uint8_t akidBuf[Crypto::kAuthorityKeyIdentifierLength]; MutableByteSpan akid(akidBuf); constexpr size_t paaCertAllocatedLen = kMaxDERCertLength; + CHIP_ERROR err = CHIP_NO_ERROR; VerifyOrExit(ExtractAKIDFromX509Cert(info.paiDerBuffer, akid) == CHIP_NO_ERROR, attestationError = AttestationVerificationResult::kPaiFormatInvalid); @@ -210,9 +213,16 @@ void DefaultDACVerifier::VerifyAttestationInformation(const DeviceAttestationVer VerifyOrExit(paaCert.Alloc(paaCertAllocatedLen), attestationError = AttestationVerificationResult::kNoMemory); paaDerBuffer = MutableByteSpan(paaCert.Get(), paaCertAllocatedLen); - VerifyOrExit(mAttestationTrustStore->GetProductAttestationAuthorityCert(akid, paaDerBuffer) == CHIP_NO_ERROR, + err = mAttestationTrustStore->GetProductAttestationAuthorityCert(akid, paaDerBuffer); + VerifyOrExit(err == CHIP_NO_ERROR || err == CHIP_ERROR_NOT_IMPLEMENTED, attestationError = AttestationVerificationResult::kPaaNotFound); + if (err == CHIP_ERROR_NOT_IMPLEMENTED) + { + VerifyOrExit(kTestAttestationTrustStore.GetProductAttestationAuthorityCert(akid, paaDerBuffer) == CHIP_NO_ERROR, + attestationError = AttestationVerificationResult::kPaaNotFound); + } + VerifyOrExit(ExtractVIDPIDFromX509Cert(paaDerBuffer, paaVidPid) == CHIP_NO_ERROR, attestationError = AttestationVerificationResult::kPaaFormatInvalid); @@ -283,17 +293,21 @@ void DefaultDACVerifier::VerifyAttestationInformation(const DeviceAttestationVer AttestationVerificationResult DefaultDACVerifier::ValidateCertificationDeclarationSignature(const ByteSpan & cmsEnvelopeBuffer, ByteSpan & certDeclBuffer) { - uint8_t certificate[Credentials::kMaxDERCertLength]; - MutableByteSpan certificateSpan(certificate); - ByteSpan skid; - - VerifyOrReturnError(CMS_ExtractKeyId(cmsEnvelopeBuffer, skid) == CHIP_NO_ERROR, + ByteSpan kid; + VerifyOrReturnError(CMS_ExtractKeyId(cmsEnvelopeBuffer, kid) == CHIP_NO_ERROR, AttestationVerificationResult::kCertificationDeclarationNoKeyId); - VerifyOrReturnError(GetCertificationDeclarationCertificate(skid, certificateSpan) == CHIP_NO_ERROR, - AttestationVerificationResult::kCertificationDeclarationNoCertificateFound); + Crypto::P256PublicKey verifyingKey; + CHIP_ERROR err = mCdKeysTrustStore.LookupVerifyingKey(kid, verifyingKey); + VerifyOrReturnError(err == CHIP_NO_ERROR, AttestationVerificationResult::kCertificationDeclarationNoCertificateFound); - VerifyOrReturnError(CMS_Verify(cmsEnvelopeBuffer, certificateSpan, certDeclBuffer) == CHIP_NO_ERROR, + // Disallow test key if support not enabled + if (mCdKeysTrustStore.IsCdTestKey(kid) && !IsCdTestKeySupported()) + { + return AttestationVerificationResult::kCertificationDeclarationNoCertificateFound; + } + + VerifyOrReturnError(CMS_Verify(cmsEnvelopeBuffer, verifyingKey, certDeclBuffer) == CHIP_NO_ERROR, AttestationVerificationResult::kCertificationDeclarationInvalidSignature); return AttestationVerificationResult::kSuccess; @@ -310,7 +324,7 @@ AttestationVerificationResult DefaultDACVerifier::ValidateCertificateDeclaration if (!firmwareInfo.empty()) { - // TODO: check if version_number field in Certification Declaration matches the one in Firmware Information. + // TODO: validate contents based on DCL } // The vendor_id field in the Certification Declaration SHALL match the VendorID attribute found in the Basic Information @@ -413,6 +427,68 @@ CHIP_ERROR DefaultDACVerifier::VerifyNodeOperationalCSRInformation(const ByteSpa return CHIP_NO_ERROR; } +bool CsaCdKeysTrustStore::IsCdTestKey(const ByteSpan & kid) const +{ + return kid.data_equal(ByteSpan{ gTestCdPubkeyKid }); +} + +CHIP_ERROR CsaCdKeysTrustStore::AddTrustedKey(const ByteSpan & kid, const Crypto::P256PublicKey & pubKey) +{ + ReturnErrorCodeIf(kid.size() > SingleKeyEntry::kMaxKidSize, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(kid.empty(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(mNumTrustedKeys == kMaxNumTrustedKeys, CHIP_ERROR_NO_MEMORY); + + auto & entry = mTrustedKeys[mNumTrustedKeys]; + + entry.kidSize = kid.size(); + memcpy(&entry.kidBuffer[0], kid.data(), kid.size()); + entry.publicKey = pubKey; + + ++mNumTrustedKeys; + return CHIP_NO_ERROR; +} + +CHIP_ERROR CsaCdKeysTrustStore::AddTrustedKey(const ByteSpan & derCertBytes) +{ + // TODO: Verify cert against CD root of trust (i.e. verify signatures). To do so, + // we are missing primitives to validate X.509 paths of total length 2. + + uint8_t kidBuf[Crypto::kSubjectKeyIdentifierLength] = { 0 }; + MutableByteSpan kidSpan{ kidBuf }; + P256PublicKey pubKey; + + VerifyOrReturnError(CHIP_NO_ERROR == Crypto::ExtractSKIDFromX509Cert(derCertBytes, kidSpan), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(CHIP_NO_ERROR == Crypto::ExtractPubkeyFromX509Cert(derCertBytes, pubKey), CHIP_ERROR_INVALID_ARGUMENT); + return AddTrustedKey(kidSpan, pubKey); +} + +CHIP_ERROR CsaCdKeysTrustStore::LookupVerifyingKey(const ByteSpan & kid, Crypto::P256PublicKey & outPubKey) const +{ + // First, search for the well known keys + for (size_t keyIdx = 0; keyIdx < gCdKids.size(); keyIdx++) + { + if (kid.data_equal(gCdKids[keyIdx])) + { + outPubKey = gCdPubkeys[keyIdx]; + return CHIP_NO_ERROR; + } + } + + // Seconds, search externally added keys + for (size_t keyIdx = 0; keyIdx < mNumTrustedKeys; keyIdx++) + { + auto & entry = mTrustedKeys[keyIdx]; + if (kid.data_equal(entry.GetKid())) + { + outPubKey = entry.publicKey; + return CHIP_NO_ERROR; + } + } + + // If we get here, the desired key was not found + return CHIP_ERROR_KEY_NOT_FOUND; +} + const AttestationTrustStore * GetTestAttestationTrustStore() { return &kTestAttestationTrustStore; diff --git a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h index 99f2fb5f2b85ae..bdf7f705dbd0f1 100644 --- a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h +++ b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h @@ -16,11 +16,44 @@ */ #pragma once +#include #include +#include +#include +#include +#include +#include namespace chip { namespace Credentials { +class CsaCdKeysTrustStore : public WellKnownKeysTrustStore +{ +public: + CsaCdKeysTrustStore() = default; + virtual ~CsaCdKeysTrustStore() = default; + + CHIP_ERROR AddTrustedKey(const ByteSpan & kid, const Crypto::P256PublicKey & pubKey) override; + CHIP_ERROR AddTrustedKey(const ByteSpan & derCertBytes) override; + CHIP_ERROR LookupVerifyingKey(const ByteSpan & kid, Crypto::P256PublicKey & outPubKey) const override; + bool IsCdTestKey(const ByteSpan & kid) const override; + +protected: + struct SingleKeyEntry + { + static constexpr size_t kMaxKidSize = 32u; + uint8_t kidBuffer[kMaxKidSize]; + size_t kidSize; + Crypto::P256PublicKey publicKey; + + ByteSpan GetKid() const { return ByteSpan{ &kidBuffer[0], kidSize }; } + }; + + static constexpr size_t kMaxNumTrustedKeys = CHIP_CONFIG_NUM_CD_KEY_SLOTS; + std::array mTrustedKeys; + size_t mNumTrustedKeys = 0; +}; + class DefaultDACVerifier : public DeviceAttestationVerifier { public: @@ -41,9 +74,12 @@ class DefaultDACVerifier : public DeviceAttestationVerifier const ByteSpan & attestationSignatureBuffer, const Crypto::P256PublicKey & dacPublicKey, const ByteSpan & csrNonce) override; + CsaCdKeysTrustStore * GetCertificationDeclarationTrustStore() override { return &mCdKeysTrustStore; } + protected: DefaultDACVerifier() {} + CsaCdKeysTrustStore mCdKeysTrustStore; const AttestationTrustStore * mAttestationTrustStore; }; diff --git a/src/credentials/attestation_verifier/DeviceAttestationVerifier.h b/src/credentials/attestation_verifier/DeviceAttestationVerifier.h index e94100f2a580f6..b79dd392aeb3bc 100644 --- a/src/credentials/attestation_verifier/DeviceAttestationVerifier.h +++ b/src/credentials/attestation_verifier/DeviceAttestationVerifier.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace chip { namespace Credentials { @@ -45,6 +46,7 @@ enum class AttestationVerificationResult : uint16_t kPaiArgumentInvalid = 204, kPaiVendorIdMismatch = 205, kPaiAuthorityNotFound = 206, + kPaiMissing = 207, kDacExpired = 300, kDacSignatureInvalid = 301, @@ -147,6 +149,73 @@ class AttestationTrustStore virtual CHIP_ERROR GetProductAttestationAuthorityCert(const ByteSpan & skid, MutableByteSpan & outPaaDerBuffer) const = 0; }; +/** + * @brief Helper utility to model obtaining verifying keys by Key ID + * + * API is synchronous. Real commissioner implementations may entirely + * hide key lookup behind the DeviceAttestationVerifier and never use this interface at all. + * It is provided as a utility to help build DeviceAttestationVerifier + * implementations suitable for testing or examples. + */ +class WellKnownKeysTrustStore +{ +public: + WellKnownKeysTrustStore() = default; + virtual ~WellKnownKeysTrustStore() = default; + + // Not copyable + WellKnownKeysTrustStore(const WellKnownKeysTrustStore &) = delete; + WellKnownKeysTrustStore & operator=(const WellKnownKeysTrustStore &) = delete; + + /** + * @brief Add a trusted key directly + * + * @param[in] kid - Key ID to use. Usually 20 bytes long, max 32 bytes. + * @param[in] pubKey - Verifying public key to attach to the key ID. + * + * @return CHIP_NO_ERROR on success, CHIP_INVALID_ARGUMENT if `kid` or `pubKey` arguments + * are not usable. CHIP_ERROR_NO_MEMORY if the trust store is full. + */ + virtual CHIP_ERROR AddTrustedKey(const ByteSpan & kid, const Crypto::P256PublicKey & pubKey) = 0; + + /** + * @brief Add a trusted key via a public certificate. + * + * The subject public key of the certificate will be used. + * The subject key ID extensions of the certificate will be the `kid`. + * + * Verification of trust chaining is at the discretion of the implementation. + * + * @param[in] derCertBytes - Certificate containing the X.509 DER certificate with the key. + * + * @return CHIP_NO_ERROR on success, CHIP_INVALID_ARGUMENT if derCertBytes is improperly + * formatted or not trusted. CHIP_ERROR_NO_MEMORY if the trust store is full. + */ + virtual CHIP_ERROR AddTrustedKey(const ByteSpan & derCertBytes) = 0; + + /** + * @brief Look-up a verifying key by Key ID + * + * Interface is synchronous. + * + * @param[in] kid Buffer containing the key identifier (KID) of the verifying key to look-up. Usually + * a SHA-1-sized buffer (20 bytes). + * @param[out] outPubKey Reference to where the verifying key found will be stored on CHIP_NO_ERROR + * + * @returns CHIP_NO_ERROR on success, CHIP_INVALID_ARGUMENT if `kid` or `pubKey` arguments + * are not usable, CHIP_ERROR_KEY_NOT_FOUND if no key is found that matches `kid`. + */ + virtual CHIP_ERROR LookupVerifyingKey(const ByteSpan & kid, Crypto::P256PublicKey & outPubKey) const = 0; + + /** + * @brief Returns true if `kid` identifies a known test key. + * + * @param kid - Key ID to use. Usually 20 bytes long, max 32 bytes. + * @return true if it's a test/development-only signing key identifier, false otherwise + */ + virtual bool IsCdTestKey(const ByteSpan & kid) const = 0; +}; + /** * @brief Basic AttestationTrustStore that holds all data within caller-owned memory. * @@ -312,9 +381,26 @@ class DeviceAttestationVerifier const Crypto::P256PublicKey & dacPublicKey, const ByteSpan & csrNonce) = 0; + /** + * @brief Get the trust store used for the attestation verifier. + * + * Returns nullptr if not supported. Be careful not to hold-on to the trust store + * for too long. It is only expected to have same lifetime as the DeviceAttestationVerifier. + * + * @return a pointer to the trust store or nullptr if none is directly accessible. + */ + virtual WellKnownKeysTrustStore * GetCertificationDeclarationTrustStore() { return nullptr; } + + void EnableCdTestKeySupport(bool enabled) { mEnableCdTestKeySupport = enabled; } + bool IsCdTestKeySupported() const { return mEnableCdTestKeySupport; } + protected: CHIP_ERROR ValidateAttestationSignature(const Crypto::P256PublicKey & pubkey, const ByteSpan & attestationElements, const ByteSpan & attestationChallenge, const Crypto::P256ECDSASignature & signature); + + // Default to support the "development" test key for legacy purposes (since the DefaultDACVerifier) + // always supported development keys. + bool mEnableCdTestKeySupport = true; }; /** diff --git a/src/credentials/attestation_verifier/FileAttestationTrustStore.cpp b/src/credentials/attestation_verifier/FileAttestationTrustStore.cpp index 147f97ce08bd04..67c729014e9536 100644 --- a/src/credentials/attestation_verifier/FileAttestationTrustStore.cpp +++ b/src/credentials/attestation_verifier/FileAttestationTrustStore.cpp @@ -16,6 +16,7 @@ */ #include "FileAttestationTrustStore.h" +#include #include #include #include @@ -41,9 +42,28 @@ const char * GetFilenameExtension(const char * filename) FileAttestationTrustStore::FileAttestationTrustStore(const char * paaTrustStorePath) { + VerifyOrReturn(paaTrustStorePath != nullptr); + + if (paaTrustStorePath != nullptr) + { + mPAADerCerts = LoadAllX509DerCerts(paaTrustStorePath); + VerifyOrReturn(paaCount()); + } + + mIsInitialized = true; +} + +std::vector> LoadAllX509DerCerts(const char * trustStorePath) +{ + std::vector> certs; + if (trustStorePath == nullptr) + { + return certs; + } + DIR * dir; - dir = opendir(paaTrustStorePath); + dir = opendir(trustStorePath); if (dir != nullptr) { // Nested directories are not handled. @@ -53,33 +73,41 @@ FileAttestationTrustStore::FileAttestationTrustStore(const char * paaTrustStoreP const char * fileExtension = GetFilenameExtension(entry->d_name); if (strncmp(fileExtension, "der", strlen("der")) == 0) { - FILE * file; - - std::array certificate; - std::string filename(paaTrustStorePath); + std::vector certificate(kMaxDERCertLength + 1); + std::string filename(trustStorePath); filename += std::string("/") + std::string(entry->d_name); - file = fopen(filename.c_str(), "rb"); - if (file != nullptr) + FILE * file = fopen(filename.c_str(), "rb"); + if (file == nullptr) { - uint32_t certificateLength = fread(certificate.data(), sizeof(uint8_t), kMaxDERCertLength, file); - if (certificateLength > 0) - { - mDerCerts.push_back(certificate); - mIsInitialized = true; - } - fclose(file); + // On bad files, just skip. + continue; } - else + + size_t certificateLength = fread(certificate.data(), sizeof(uint8_t), certificate.size(), file); + if ((certificateLength > 0) && (certificateLength <= kMaxDERCertLength)) { - Cleanup(); - break; + certificate.resize(certificateLength); + // Only accumulate certificate if it has a subject key ID extension + { + uint8_t kidBuf[Crypto::kSubjectKeyIdentifierLength] = { 0 }; + MutableByteSpan kidSpan{ kidBuf }; + ByteSpan certSpan{ certificate.data(), certificate.size() }; + + if (CHIP_NO_ERROR == Crypto::ExtractSKIDFromX509Cert(certSpan, kidSpan)) + { + certs.push_back(certificate); + } + } } + fclose(file); } } closedir(dir); } + + return certs; } FileAttestationTrustStore::~FileAttestationTrustStore() @@ -89,18 +117,25 @@ FileAttestationTrustStore::~FileAttestationTrustStore() void FileAttestationTrustStore::Cleanup() { - mDerCerts.clear(); + mPAADerCerts.clear(); mIsInitialized = false; } CHIP_ERROR FileAttestationTrustStore::GetProductAttestationAuthorityCert(const ByteSpan & skid, MutableByteSpan & outPaaDerBuffer) const { - VerifyOrReturnError(!mDerCerts.empty(), CHIP_ERROR_CA_CERT_NOT_FOUND); + // If the constructor has not tried to initialize the PAA certificates database, return CHIP_ERROR_NOT_IMPLEMENTED to use the + // testing trust store if the DefaultAttestationVerifier is in use. + if (mIsInitialized && paaCount() == 0) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + VerifyOrReturnError(!mPAADerCerts.empty(), CHIP_ERROR_CA_CERT_NOT_FOUND); VerifyOrReturnError(!skid.empty() && (skid.data() != nullptr), CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(skid.size() == Crypto::kSubjectKeyIdentifierLength, CHIP_ERROR_INVALID_ARGUMENT); - for (auto candidate : mDerCerts) + for (auto candidate : mPAADerCerts) { uint8_t skidBuf[Crypto::kSubjectKeyIdentifierLength] = { 0 }; MutableByteSpan candidateSkidSpan{ skidBuf }; diff --git a/src/credentials/attestation_verifier/FileAttestationTrustStore.h b/src/credentials/attestation_verifier/FileAttestationTrustStore.h index 090be169367c63..0446d7a6ebb6b8 100644 --- a/src/credentials/attestation_verifier/FileAttestationTrustStore.h +++ b/src/credentials/attestation_verifier/FileAttestationTrustStore.h @@ -25,19 +25,31 @@ namespace chip { namespace Credentials { +/** + * @brief Load all X.509 DER certificates in a given path. + * + * Silently ignores non-X.509 files and X.509 files without a subject key identifier. + * + * Returns an empty vector if no files are found or unrecoverable errors arise. + * + * @param trustStorePath - path from where to search for certificates. + * @return a vector of certificate DER data + */ +std::vector> LoadAllX509DerCerts(const char * trustStorePath); + class FileAttestationTrustStore : public AttestationTrustStore { public: - FileAttestationTrustStore(const char * paaTrustStorePath); + FileAttestationTrustStore(const char * paaTrustStorePath = nullptr); ~FileAttestationTrustStore(); - bool IsInitialized() { return mIsInitialized; } - CHIP_ERROR GetProductAttestationAuthorityCert(const ByteSpan & skid, MutableByteSpan & outPaaDerBuffer) const override; - size_t size() const { return mDerCerts.size(); } + + bool IsInitialized() const { return mIsInitialized; } + size_t paaCount() const { return mPAADerCerts.size(); }; protected: - std::vector> mDerCerts; + std::vector> mPAADerCerts; private: bool mIsInitialized = false; diff --git a/src/credentials/tests/TestCertificationDeclaration.cpp b/src/credentials/tests/TestCertificationDeclaration.cpp index cda5f90b05124e..0a1d3aa33c9a3f 100644 --- a/src/credentials/tests/TestCertificationDeclaration.cpp +++ b/src/credentials/tests/TestCertificationDeclaration.cpp @@ -27,7 +27,10 @@ #include #include +#include +#include #include +#include #include #include @@ -150,6 +153,45 @@ static constexpr uint8_t sTestCMS_SignedMessage02[] = { 0x36, 0x5c, 0x6e, 0xd5, 0x44, 0x08, 0x6d, 0x10, 0x1a, 0xfd, 0xaf, 0x07, 0x9a, 0x2c, 0x23, 0xe0, 0xde }; +// Generated an untrusted CD key, and then a CD +// +// openssl ecparam -name prime256v1 -genkey -noout -out UntrustedCDKey.pem +// openssl req -x509 -key UntrustedCDKey.pem -keyform PEM -out UntrustedCDCert.pem -sha256 -days 3650 +// out/host/chip-cert gen-cd -C UntrustedCDCert.pem -K UntrustedCDKey.pem --out UntrustedCD.bin -f 1 -V FFF1 -p 8000 -p 8001 -p 8002 +// -p 8003 -p 8004 -p 8005 -p 8006 -p 8007 -p 8008 -p 8009 -p 800a -p 800b -p 800c -p 800d -p 800e -p 800f -p 8010 -p 8011 -p 8012 +// -p 8013 -p 8014 -p 8015 -p 8016 -p 8017 -p 8018 -p 8019 -p 801a -p 801b -p 801c -p 801d -p 801e -p 801f -d 0016 -c +// "ZIG0000000000000000" -l 0 -i 0 -n 0001 -t 0 +// +static constexpr uint8_t gUntrustedCd[333] = { + 0x30, 0x82, 0x01, 0x49, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x82, 0x01, 0x3a, 0x30, 0x82, + 0x01, 0x36, 0x02, 0x01, 0x03, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, + 0x81, 0xa2, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x81, 0x94, 0x04, 0x81, 0x91, 0x15, 0x24, + 0x00, 0x01, 0x25, 0x01, 0xf1, 0xff, 0x36, 0x02, 0x05, 0x00, 0x80, 0x05, 0x01, 0x80, 0x05, 0x02, 0x80, 0x05, 0x03, 0x80, 0x05, + 0x04, 0x80, 0x05, 0x05, 0x80, 0x05, 0x06, 0x80, 0x05, 0x07, 0x80, 0x05, 0x08, 0x80, 0x05, 0x09, 0x80, 0x05, 0x0a, 0x80, 0x05, + 0x0b, 0x80, 0x05, 0x0c, 0x80, 0x05, 0x0d, 0x80, 0x05, 0x0e, 0x80, 0x05, 0x0f, 0x80, 0x05, 0x10, 0x80, 0x05, 0x11, 0x80, 0x05, + 0x12, 0x80, 0x05, 0x13, 0x80, 0x05, 0x14, 0x80, 0x05, 0x15, 0x80, 0x05, 0x16, 0x80, 0x05, 0x17, 0x80, 0x05, 0x18, 0x80, 0x05, + 0x19, 0x80, 0x05, 0x1a, 0x80, 0x05, 0x1b, 0x80, 0x05, 0x1c, 0x80, 0x05, 0x1d, 0x80, 0x05, 0x1e, 0x80, 0x05, 0x1f, 0x80, 0x18, + 0x24, 0x03, 0x16, 0x2c, 0x04, 0x13, 0x5a, 0x49, 0x47, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x24, 0x05, 0x00, 0x24, 0x06, 0x00, 0x24, 0x07, 0x01, 0x24, 0x08, 0x00, 0x18, 0x31, 0x7d, 0x30, 0x7b, + 0x02, 0x01, 0x03, 0x80, 0x14, 0x75, 0xe3, 0x06, 0x0e, 0x0f, 0xce, 0x28, 0x69, 0x5d, 0x19, 0x75, 0x43, 0x32, 0xbb, 0xc7, 0x9b, + 0xeb, 0x3d, 0x60, 0x6c, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0a, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x04, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xf9, 0xd5, 0x91, 0x66, 0xca, 0xab, + 0x43, 0x82, 0xa7, 0x47, 0x24, 0xe6, 0x06, 0xbf, 0x1e, 0x2b, 0x4e, 0x7a, 0xb4, 0xf3, 0x04, 0x29, 0x21, 0x2b, 0x60, 0xfd, 0xa0, + 0x16, 0xea, 0xdc, 0x7d, 0x25, 0x02, 0x20, 0x62, 0xf1, 0x89, 0xfe, 0x4e, 0x60, 0x28, 0xd5, 0xc8, 0x42, 0x6a, 0xb7, 0x7e, 0xe5, + 0x1e, 0xf5, 0x68, 0x00, 0x23, 0x86, 0xba, 0x39, 0xe7, 0xab, 0x2d, 0xe5, 0x71, 0xe5, 0x36, 0x4e, 0xd0, 0x38, +}; + +static constexpr uint8_t gUntrustedCdVerifyingKeyBytes[65] = { 0x04, 0xbc, 0x87, 0x13, 0x3a, 0x19, 0x16, 0x87, 0x04, 0x34, 0x1b, + 0x78, 0xc5, 0x70, 0x41, 0x21, 0x14, 0xbb, 0xe5, 0x3f, 0x62, 0x45, + 0x70, 0xe8, 0xf2, 0x37, 0x78, 0x77, 0x1a, 0xf3, 0x5c, 0xd8, 0x04, + 0x21, 0xc8, 0x2d, 0x40, 0x7f, 0xee, 0x37, 0xf5, 0xa5, 0x9f, 0x17, + 0x26, 0x33, 0x00, 0x4d, 0xf7, 0x66, 0xa3, 0x3a, 0x50, 0x75, 0x9f, + 0xcf, 0xd2, 0xb2, 0x1b, 0x5e, 0x58, 0x75, 0x08, 0x82, 0x3e }; +static const P256PublicKey gUntrustedCdVerifyingKey(gUntrustedCdVerifyingKeyBytes); + +static constexpr uint8_t gUntrustedCdKid[20] = { 0x75, 0xE3, 0x06, 0x0E, 0x0F, 0xCE, 0x28, 0x69, 0x5D, 0x19, + 0x75, 0x43, 0x32, 0xBB, 0xC7, 0x9B, 0xEB, 0x3D, 0x60, 0x6C }; + struct TestCase { ByteSpan signerCert; @@ -464,6 +506,53 @@ static void TestCD_EncodeDecode_Random(nlTestSuite * inSuite, void * inContext) } } +static void TestCD_DefaultCdTrustStore(nlTestSuite * inSuite, void * inContext) +{ + chip::Credentials::CsaCdKeysTrustStore trustStore; + + // Make sure that for an untrusted CD, whose key is not in truststore, we cannot find the key. + { + ByteSpan signerKeyIdOut; + NL_TEST_ASSERT_SUCCESS(inSuite, CMS_ExtractKeyId(ByteSpan(gUntrustedCd), signerKeyIdOut)); + NL_TEST_ASSERT(inSuite, !trustStore.IsCdTestKey(signerKeyIdOut)); + + P256PublicKey pubKey; + NL_TEST_ASSERT(inSuite, CHIP_ERROR_KEY_NOT_FOUND == trustStore.LookupVerifyingKey(signerKeyIdOut, pubKey)); + } + + // Verify that a payload known to use the test key passes verification w/ default truststore + { + const auto & testCase = sTestCases[0]; + ByteSpan signerKeyIdOut; + NL_TEST_ASSERT_SUCCESS(inSuite, CMS_ExtractKeyId(testCase.cdCMSSigned, signerKeyIdOut)); + NL_TEST_ASSERT(inSuite, trustStore.IsCdTestKey(signerKeyIdOut)); + + P256PublicKey pubKey; + ByteSpan cdContentOut; + NL_TEST_ASSERT_SUCCESS(inSuite, trustStore.LookupVerifyingKey(signerKeyIdOut, pubKey)); + + NL_TEST_ASSERT(inSuite, CMS_Verify(testCase.cdCMSSigned, pubKey, cdContentOut) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, testCase.cdContent.data_equal(cdContentOut)); + } + + // Verify that after adding the verifying key to the trust store, it is now possible to + // verify the CD signature. + { + P256PublicKey pubKey; + ByteSpan cdContentOut; + NL_TEST_ASSERT_SUCCESS(inSuite, trustStore.AddTrustedKey(ByteSpan(gUntrustedCdKid), gUntrustedCdVerifyingKey)); + + ByteSpan signerKeyIdOut; + NL_TEST_ASSERT_SUCCESS(inSuite, CMS_ExtractKeyId(ByteSpan(gUntrustedCd), signerKeyIdOut)); + NL_TEST_ASSERT(inSuite, signerKeyIdOut.data_equal(ByteSpan(gUntrustedCdKid))); + + NL_TEST_ASSERT_SUCCESS(inSuite, trustStore.LookupVerifyingKey(signerKeyIdOut, pubKey)); + NL_TEST_ASSERT(inSuite, pubKey.Matches(gUntrustedCdVerifyingKey)); + + NL_TEST_ASSERT_SUCCESS(inSuite, CMS_Verify(ByteSpan(gUntrustedCd), pubKey, cdContentOut)); + } +} + #define NL_TEST_DEF_FN(fn) NL_TEST_DEF("Test " #fn, fn) /** * Test Suite. It lists all the test functions. @@ -474,6 +563,7 @@ static const nlTest sTests[] = { NL_TEST_DEF_FN(TestCD_EncodeDecode), NL_TEST_DEF_FN(TestCD_CMSVerifyAndExtract), NL_TEST_DEF_FN(TestCD_CertificationElementsDecoder), NL_TEST_DEF_FN(TestCD_EncodeDecode_Random), + NL_TEST_DEF_FN(TestCD_DefaultCdTrustStore), NL_TEST_SENTINEL() }; int TestCertificationDeclaration(void) diff --git a/src/darwin/Framework/CHIP/MTRAttestationTrustStoreBridge.mm b/src/darwin/Framework/CHIP/MTRAttestationTrustStoreBridge.mm index ee88d420624d4a..f968ca4d617de9 100644 --- a/src/darwin/Framework/CHIP/MTRAttestationTrustStoreBridge.mm +++ b/src/darwin/Framework/CHIP/MTRAttestationTrustStoreBridge.mm @@ -16,8 +16,7 @@ */ #import "MTRAttestationTrustStoreBridge.h" - -static chip::ByteSpan asByteSpan(NSData * value) { return chip::ByteSpan(static_cast(value.bytes), value.length); } +#import "NSDataSpanConversion.h" CHIP_ERROR MTRAttestationTrustStoreBridge::GetProductAttestationAuthorityCert( const chip::ByteSpan & skid, chip::MutableByteSpan & outPaaDerBuffer) const @@ -29,7 +28,7 @@ for (paaIdx = 0; paaIdx < mPaaCerts.count; ++paaIdx) { uint8_t skidBuf[chip::Crypto::kSubjectKeyIdentifierLength] = { 0 }; - candidate = asByteSpan(mPaaCerts[paaIdx]); + candidate = AsByteSpan(mPaaCerts[paaIdx]); chip::MutableByteSpan candidateSkidSpan { skidBuf }; VerifyOrReturnError( CHIP_NO_ERROR == chip::Crypto::ExtractSKIDFromX509Cert(candidate, candidateSkidSpan), CHIP_ERROR_INTERNAL); diff --git a/src/darwin/Framework/CHIP/MTRControllerFactory.h b/src/darwin/Framework/CHIP/MTRControllerFactory.h index 8e8eb8a9fcf318..508cf369374bae 100644 --- a/src/darwin/Framework/CHIP/MTRControllerFactory.h +++ b/src/darwin/Framework/CHIP/MTRControllerFactory.h @@ -51,6 +51,12 @@ NS_ASSUME_NONNULL_BEGIN * */ @property (nonatomic, copy, nullable) NSArray * paaCerts; +/* + * The Certificate Declaration certificates that are trusted to sign + * device attestation information. Defaults to nil. + * + */ +@property (nonatomic, copy, nullable) NSArray * cdCerts; /* * The network port to bind to. If not specified, an ephemeral port will be * used. diff --git a/src/darwin/Framework/CHIP/MTRControllerFactory.mm b/src/darwin/Framework/CHIP/MTRControllerFactory.mm index 337299bde13570..f769548a2d7f24 100644 --- a/src/darwin/Framework/CHIP/MTRControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRControllerFactory.mm @@ -54,6 +54,7 @@ static NSString * const kErrorControllerFactoryInit = @"Init failure while initializing controller factory"; static NSString * const kErrorKeystoreInit = @"Init failure while initializing persistent storage keystore"; static NSString * const kErrorCertStoreInit = @"Init failure while initializing persistent storage operational certificate store"; +static NSString * const kErrorCDCertStoreInit = @"Init failure while initializing Certificate Declaration Signing Keys store"; static NSString * const kErrorOtaProviderInit = @"Init failure while creating an OTA provider delegate"; @interface MTRControllerFactory () @@ -274,6 +275,22 @@ - (BOOL)startup:(MTRControllerFactoryParams *)startupParams return; } + if (startupParams.cdCerts) { + auto cdTrustStore = _deviceAttestationVerifier->GetCertificationDeclarationTrustStore(); + if (cdTrustStore == nullptr) { + MTR_LOG_ERROR("Error: %@", kErrorCDCertStoreInit); + return; + } + + for (NSData * cdSigningCert in startupParams.cdCerts) { + errorCode = cdTrustStore->AddTrustedKey(AsByteSpan(cdSigningCert)); + if (errorCode != CHIP_NO_ERROR) { + MTR_LOG_ERROR("Error: %@", kErrorCDCertStoreInit); + return; + } + } + } + chip::Controller::FactoryInitParams params; if (startupParams.port != nil) { params.listenPort = [startupParams.port unsignedShortValue]; @@ -612,6 +629,7 @@ - (instancetype)initWithStorage:(id)storageDelegat _storageDelegate = storageDelegate; _otaProviderDelegate = nil; _paaCerts = nil; + _cdCerts = nil; _port = nil; _startServer = NO; diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 95fea208f584b2..60865613b45efe 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1282,6 +1282,15 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_SETUP_CODE_PAIRER_DISCOVERY_TIMEOUT_SECS 30 #endif // CHIP_CONFIG_SETUP_CODE_PAIRER_DISCOVERY_TIMEOUT_SECS +/** + * @def CHIP_CONFIG_NUM_CD_KEY_SLOTS + * + * @brief Number of custom CD signing keys supported by default CD keystore + * + */ +#ifndef CHIP_CONFIG_NUM_CD_KEY_SLOTS +#define CHIP_CONFIG_NUM_CD_KEY_SLOTS 5 +#endif // CHIP_CONFIG_NUM_CD_KEY_SLOTS /** * @} */