| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "platform_case_auth_delegate.h" |
| |
| #include <fuchsia/weave/cpp/fidl.h> |
| #include <lib/fit/defer.h> |
| |
| #include <Weave/Profiles/security/WeaveSig.h> |
| #include <sdk/lib/syslog/cpp/macros.h> |
| |
| #include "src/connectivity/weave/adaptation/weave_device_platform_error.h" |
| #include "utils.h" |
| |
| namespace nl { |
| namespace Weave { |
| namespace DeviceLayer { |
| namespace Internal { |
| |
| namespace { |
| using namespace ::nl::Weave::TLV; |
| using namespace ::nl::Weave::Profiles; |
| using namespace ::nl::Weave::Profiles::Security; |
| |
| // TODO(fxbug.dev/51130): Allow build-time configuration of these values. |
| constexpr size_t kMaxCerts = 10; |
| constexpr size_t kMaxServiceConfigSize = 10000; |
| constexpr size_t kCertDecodeBufferSize = 5000; |
| |
| } // namespace |
| |
| PlatformCASEAuthDelegate gPlatformCASEAuthDelegate; |
| |
| // Implementation of Internal::InitCASEAuthDelegate as defined in the |
| // openweave-core adaptation layer. |
| WEAVE_ERROR InitCASEAuthDelegate() { |
| new (&gPlatformCASEAuthDelegate) |
| PlatformCASEAuthDelegate(sys::ComponentContext::CreateAndServeOutgoingDirectory()); |
| SecurityMgr.SetCASEAuthDelegate(&gPlatformCASEAuthDelegate); |
| return WEAVE_NO_ERROR; |
| } |
| |
| PlatformCASEAuthDelegate::PlatformCASEAuthDelegate() : PlatformCASEAuthDelegate(nullptr) {} |
| |
| PlatformCASEAuthDelegate::PlatformCASEAuthDelegate(std::unique_ptr<sys::ComponentContext> context) |
| : context_(std::move(context)) {} |
| |
| WEAVE_ERROR PlatformCASEAuthDelegate::EncodeNodePayload(const BeginSessionContext& msg_ctx, |
| uint8_t* payload_buf, |
| uint16_t payload_buf_size, |
| uint16_t& payload_len) { |
| WEAVE_ERROR error = WEAVE_NO_ERROR; |
| size_t device_desc_len; |
| |
| error = ConfigurationMgr().GetDeviceDescriptorTLV(payload_buf, (size_t)payload_buf_size, |
| device_desc_len); |
| if (error != WEAVE_NO_ERROR) { |
| return error; |
| } |
| |
| payload_len = device_desc_len; |
| return WEAVE_NO_ERROR; |
| } |
| |
| WEAVE_ERROR PlatformCASEAuthDelegate::EncodeNodeCertInfo(const BeginSessionContext& msg_ctx, |
| TLVWriter& writer) { |
| std::vector<uint8_t> device_cert; |
| std::vector<uint8_t> device_ica_certs; |
| size_t device_cert_size = 0; |
| size_t device_ica_certs_size = 0; |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| |
| // Fetch the provided device certificate. |
| err = ConfigurationMgr().GetDeviceCertificate(nullptr, 0, device_cert_size); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| device_cert.resize(device_cert_size); |
| err = ConfigurationMgr().GetDeviceCertificate(device_cert.data(), device_cert_size, |
| device_cert_size); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| // Fetch any intermediate CA certificates. |
| err = ConfigurationMgr().GetDeviceIntermediateCACerts(nullptr, 0, device_ica_certs_size); |
| if (err == WEAVE_NO_ERROR) { |
| device_ica_certs.resize(device_ica_certs_size); |
| err = ConfigurationMgr().GetDeviceIntermediateCACerts( |
| device_ica_certs.data(), device_ica_certs_size, device_ica_certs_size); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| } else if (err != WEAVE_DEVICE_ERROR_CONFIG_NOT_FOUND) { |
| return err; |
| } |
| |
| // Encode the certificate information into the provided TLVWriter. |
| return Profiles::Security::CASE::EncodeCASECertInfo( |
| writer, device_cert.data(), device_cert_size, device_ica_certs.data(), device_ica_certs_size); |
| } |
| |
| WEAVE_ERROR PlatformCASEAuthDelegate::GenerateNodeSignature(const BeginSessionContext& msg_ctx, |
| const uint8_t* msg_hash, |
| uint8_t msg_hash_len, TLVWriter& writer, |
| uint64_t tag) { |
| fuchsia::weave::Signer_SignHash_Result result; |
| std::vector<uint8_t>* output; |
| zx_status_t status; |
| std::vector<uint8_t> signing_key; |
| |
| // Using a private key directly is intended only for test purposes. |
| status = ConfigurationMgrImpl().GetPrivateKeyForSigning(&signing_key); |
| if (status == ZX_OK) { |
| status = GenerateAndEncodeWeaveECDSASignature(writer, tag, msg_hash, msg_hash_len, |
| signing_key.data(), signing_key.size()); |
| secure_memset(signing_key.data(), 0, signing_key.size()); |
| signing_key.clear(); |
| return status; |
| } |
| |
| // If private key is not present, continue with the signer, else return the error |
| // encountered in reading the private key. |
| if (status != ZX_ERR_NOT_FOUND) { |
| FX_LOGS(ERROR) << "Failed reading private key: " << zx_status_get_string(status); |
| return status; |
| } |
| |
| fuchsia::weave::SignerSyncPtr signer; |
| if ((status = context_->svc()->Connect(signer.NewRequest())) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to connect to signer: " << status; |
| return status; |
| } |
| |
| std::vector<uint8_t> hash(msg_hash, msg_hash + (msg_hash_len * sizeof(uint8_t))); |
| if ((status = signer->SignHash(hash, &result)) != ZX_OK || !result.is_response()) { |
| FX_LOGS(ERROR) << "Failed to sign hash: " << status; |
| return status; |
| } |
| |
| output = &result.response().signature; |
| return ConvertECDSASignature_DERToWeave(output->data(), output->size(), writer, tag); |
| } |
| |
| WEAVE_ERROR PlatformCASEAuthDelegate::BeginValidation(const BeginSessionContext& msg_ctx, |
| ValidationContext& valid_ctx, |
| WeaveCertificateSet& cert_set) { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| size_t service_config_len = 0; |
| uint64_t now_ms; |
| |
| service_config_.clear(); |
| service_config_.resize(kMaxServiceConfigSize); |
| |
| err = cert_set.Init(kMaxCerts, kCertDecodeBufferSize); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| auto release_cert_set = fit::defer([&] { cert_set.Release(); }); |
| |
| // Read the service config data. |
| err = ConfigurationMgr().GetServiceConfig(service_config_.data(), service_config_.size(), |
| service_config_len); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| // Load the list of trusted root certificates from the service config. |
| err = LoadCertsFromServiceConfig(service_config_.data(), service_config_len, cert_set); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| // Scan the list of trusted certs loaded from the service config. If the list |
| // contains a general certificate with a CommonName subject, presume this is |
| // the access token certificate. |
| for (uint8_t cert_index = 0; cert_index < cert_set.CertCount; cert_index++) { |
| WeaveCertificateData* cert = &cert_set.Certs[cert_index]; |
| if ((cert->CertFlags & kCertFlag_IsTrusted) != 0 && cert->CertType == kCertType_General && |
| cert->SubjectDN.AttrOID == ASN1::kOID_AttributeType_CommonName) { |
| cert->CertType = kCertType_AccessToken; |
| } |
| } |
| |
| memset(&valid_ctx, 0, sizeof(valid_ctx)); |
| |
| // Set the effective time for certificate validation. Use the current time if |
| // the system's real time clock is synchronized, but otherwise use the |
| // firmware build time and arrange to ignore the 'not before' date in the |
| // peer's certificate. |
| err = System::Layer::GetClock_RealTimeMS(now_ms); |
| if (err == WEAVE_NO_ERROR) { |
| // TODO(fxbug.dev/51890): The default implementation of GetClock_RealTimeMS only returns |
| // not-synced if the value is before Jan 1, 2000. Use the UTC fidl instead |
| // to confirm whether the clock source is from some external source. |
| valid_ctx.EffectiveTime = SecondsSinceEpochToPackedCertTime((uint32_t)(now_ms / 1000)); |
| } else if (err == WEAVE_SYSTEM_ERROR_REAL_TIME_NOT_SYNCED) { |
| // TODO(fxbug.dev/51890): Acquire the firmware build time, for now we set it to Jan 1, 2020 |
| // as sane default time. |
| valid_ctx.EffectiveTime = SecondsSinceEpochToPackedCertTime(1577836800U); |
| valid_ctx.ValidateFlags |= kValidateFlag_IgnoreNotBefore; |
| FX_LOGS(WARNING) << "Real time clock not synchronized, using default time for cert validation."; |
| } |
| |
| valid_ctx.RequiredKeyUsages = kKeyUsageFlag_DigitalSignature; |
| valid_ctx.RequiredKeyPurposes = |
| msg_ctx.IsInitiator() ? kKeyPurposeFlag_ServerAuth : kKeyPurposeFlag_ClientAuth; |
| release_cert_set.cancel(); |
| return WEAVE_NO_ERROR; |
| } |
| |
| WEAVE_ERROR PlatformCASEAuthDelegate::HandleValidationResult(const BeginSessionContext& msg_ctx, |
| ValidationContext& valid_ctx, |
| WeaveCertificateSet& cert_set, |
| WEAVE_ERROR& valid_res) { |
| // If an error was already detected in a previous stage, return it as-is. |
| if (valid_res != WEAVE_NO_ERROR) { |
| return WEAVE_NO_ERROR; |
| } |
| |
| uint64_t cert_id = valid_ctx.SigningCert->SubjectDN.AttrValue.WeaveId; |
| switch (valid_ctx.SigningCert->CertType) { |
| // If the peer authenticated with a device certificate: |
| case kCertType_Device: |
| // Reject the peer if the certificate node ID and peer node ID do not match. |
| if (cert_id != msg_ctx.PeerNodeId) { |
| valid_res = WEAVE_ERROR_WRONG_CERTIFICATE_SUBJECT; |
| } |
| break; |
| // If the peer authenticated with a service endpoint certificate: |
| case kCertType_ServiceEndpoint: |
| // Reject the peer if the certificate node ID and peer node ID do not match. |
| if (cert_id != msg_ctx.PeerNodeId) { |
| valid_res = WEAVE_ERROR_WRONG_CERTIFICATE_SUBJECT; |
| } |
| // Reject the peer if they are initiating the session. Service endpoint |
| // certificates cannot be used to initiate sessions to other nodes, only |
| // to respond. |
| if (!msg_ctx.IsInitiator()) { |
| valid_res = WEAVE_ERROR_CERT_USAGE_NOT_ALLOWED; |
| } |
| break; |
| // If the peer authenticated with an access token certificate: |
| case kCertType_AccessToken: |
| // Reject the peer if they are the session responder. Access token |
| // certificates can only be used to initiate sessions. |
| if (msg_ctx.IsInitiator()) { |
| valid_res = WEAVE_ERROR_CERT_USAGE_NOT_ALLOWED; |
| } |
| break; |
| default: |
| valid_res = WEAVE_ERROR_CERT_USAGE_NOT_ALLOWED; |
| } |
| return WEAVE_NO_ERROR; |
| } |
| |
| void PlatformCASEAuthDelegate::EndValidation(const BeginSessionContext& msg_ctx, |
| ValidationContext& valid_ctx, |
| WeaveCertificateSet& cert_set) { |
| service_config_.clear(); |
| } |
| |
| WEAVE_ERROR PlatformCASEAuthDelegate::LoadCertsFromServiceConfig(const uint8_t* service_config, |
| uint16_t service_config_len, |
| WeaveCertificateSet& cert_set) { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| TLVReader reader; |
| TLVType top_level_container; |
| |
| reader.Init(service_config, service_config_len); |
| reader.ImplicitProfileId = kWeaveProfile_ServiceProvisioning; |
| |
| err = reader.Next(kTLVType_Structure, ProfileTag(kWeaveProfile_ServiceProvisioning, |
| ServiceProvisioning::kTag_ServiceConfig)); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| err = reader.EnterContainer(top_level_container); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| err = reader.Next(kTLVType_Array, ContextTag(ServiceProvisioning::kTag_ServiceConfig_CACerts)); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| err = cert_set.LoadCerts(reader, kDecodeFlag_IsTrusted); |
| if (err != WEAVE_NO_ERROR) { |
| return err; |
| } |
| |
| return WEAVE_NO_ERROR; |
| } |
| |
| } // namespace Internal |
| } // namespace DeviceLayer |
| } // namespace Weave |
| } // namespace nl |