blob: 4b31897fad3a9715b2a0a17bd6cff4daf767b084 [file] [log] [blame]
// 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