blob: 1f4a6e6a6047aed5ae172f81086ee4f628516ab7 [file] [log] [blame]
/*
*
* Copyright (c) 2016 Nest Labs, Inc.
* All rights reserved.
*
* This document is the property of Nest. It is considered
* confidential and proprietary information.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Nest.
*
*/
/**
* @file
* This file implements the Dropcam Legacy Pairing Profile, which provides
* operations and utilities used to pair both in-field and out-of-box
* Nest Cam devices with Weave via the camera cloud service.
*/
#include <Weave/Core/WeaveCore.h>
#include <Weave/Core/WeaveTLV.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Core/WeaveEncoding.h>
#include <Weave/Profiles/WeaveProfiles.h>
#include "DropcamLegacyPairing.h"
#include <Weave/Profiles/common/CommonProfile.h>
#include <Weave/Support/logging/WeaveLogging.h>
#include <Weave/Support/ProfileStringSupport.hpp>
/**
* @def REQUIRE_AUTH_DROPCAM_LEGACY_PAIRING
*
* @brief
* Enable (1) or disable (0) support for client requests to the
* Dropcam Legacy Pairing server via an authenticated session. See
* also #WEAVE_CONFIG_REQUIRE_AUTH in WeaveConfig.h.
*
* @note This preprocessor define shall be deasserted for development
* and testing purposes only. No Weave-enabled device shall
* be certified without this asserted.
*
*/
#define REQUIRE_AUTH_DROPCAM_LEGACY_PAIRING 1
namespace nl {
namespace Weave {
namespace Profiles {
namespace Vendor {
namespace Nestlabs {
namespace DropcamLegacyPairing {
using namespace nl::Weave::Crypto;
using namespace nl::Weave::Encoding;
using namespace nl::Weave::TLV;
// Preprocessor Macros
#define kWeave_VendorNameString_Nest "Nest"
#define kWeave_ProfileNameString_DropcamLegacyPairing \
kWeave_VendorNameString_Nest ":DropcamLegacyPairing"
// Forward Declarations
static const char *GetDropcamLegacyPairingMessageName(uint32_t inProfileId, uint8_t inMsgType);
static const char *GetDropcamLegacyPairingProfileName(uint32_t inProfileId);
static void _DropcamLegacyPairingProfileStringInit(void) __attribute__((constructor));
static void _DropcamLegacyPairingProfileStringDestroy(void) __attribute__((destructor));
// Globals
/**
* This structure provides storage for callbacks associated for
* returning human-readable support strings associated with the
* profile.
*/
static const Weave::Support::ProfileStringInfo sDropcamLegacyPairingProfileStringInfo = {
kWeaveProfile_DropcamLegacyPairing,
GetDropcamLegacyPairingMessageName,
GetDropcamLegacyPairingProfileName,
NULL
};
/**
* Context for registering and deregistering callbacks associated
* with for returning human-readable support strings associated with
* the profile.
*/
static Weave::Support::ProfileStringContext sDropcamLegacyPairingProfileStringContext = {
NULL,
sDropcamLegacyPairingProfileStringInfo
};
/**
* One time, yet reentrant, initializer for registering Weave Dropcam
* Legacy Pairing profile callbacks for returning human-readable
* support strings associated with the profile.
*/
static void _DropcamLegacyPairingProfileStringInit(void)
{
(void)Weave::Support::RegisterProfileStringInfo(sDropcamLegacyPairingProfileStringContext);
}
/**
* One time, yet reentrant, deinitializer for unregistering Weave
* Dropcam Legacy Pairing profile callbacks for returning
* human-readable support strings associated with the profile.
*/
static void _DropcamLegacyPairingProfileStringDestroy(void)
{
(void)Weave::Support::UnregisterProfileStringInfo(sDropcamLegacyPairingProfileStringContext);
}
/**
* @brief
* Callback function that returns a human-readable NULL-terminated
* C string describing the message type associated with this
* profile.
*
* This callback, when registered, is invoked when a human-readable
* NULL-terminated C string is needed to describe the message type
* associated with this profile.
*
* @param[in] inProfileId The profile identifier associated with the
* specified message type.
*
* @param[in] inMsgType The message type for which a human-readable
* descriptive string is sought.
*
* @return a pointer to the NULL-terminated C string if a match is
* found; otherwise, NULL.
*
*/
static const char *GetDropcamLegacyPairingMessageName(uint32_t inProfileId, uint8_t inMsgType)
{
const char *result = NULL;
switch (inProfileId) {
case kWeaveProfile_DropcamLegacyPairing:
switch (inMsgType) {
case DropcamLegacyPairing::kMsgType_CameraAuthDataRequest:
result = "CameraAuthDataRequest";
break;
case DropcamLegacyPairing::kMsgType_CameraAuthDataResponse:
result = "CameraAuthDataResponse";
break;
}
break;
}
return (result);
}
/**
* @brief
* Callback function that returns a human-readable NULL-terminated
* C string describing the profile with this profile.
*
* This callback, when registered, is invoked when a human-readable
* NULL-terminated C string is needed to describe this profile.
*
* @param[in] inProfileId The profile identifier for which a human-readable
* descriptive string is sought.
*
* @return a pointer to the NULL-terminated C string if a match is
* found; otherwise, NULL.
*
*/
static const char *GetDropcamLegacyPairingProfileName(uint32_t inProfileId)
{
const char *result = NULL;
switch (inProfileId)
{
case kWeaveProfile_DropcamLegacyPairing:
result = kWeave_ProfileNameString_DropcamLegacyPairing;
break;
}
return (result);
}
/**
* Utility function to encode CameraAuthDataRequest message payload
*
* @param[in] buf A pointer to the Camera Auth Data Request message payload buffer.
*
* @param[in] nonce A pointer to the camera pairing nonce, formatted as a NULL-terminated UTF-8 string.
*
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred preventing encoding of the message payload.
*/
WEAVE_ERROR EncodeCameraAuthDataRequest(PacketBuffer *buf, const char *nonce)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
TLVWriter writer;
writer.Init(buf, buf->MaxDataLength());
err = writer.PutString(AnonymousTag, nonce);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
exit:
return err;
}
/**
* Utility function to decode CameraAuthDataResponse message payload
*
* @param[in] buf A pointer to the Camera Auth Data Response message payload buffer.
*
* @param[in] macAddress A byte array buffer for the camera's EUI-48 WiFi MAC address.
*
* @param[in] hmac A reference to the provided HMAC return buffer. HMAC returned as raw byte
* array which may contain non-ASCII/Unicode characters.
*
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred preventing decoding of the message payload.
*/
WEAVE_ERROR DecodeCameraAuthDataResponse(PacketBuffer *buf, uint8_t (&macAddress)[EUI48_LEN], uint8_t (&hmac)[HMAC_BUF_LEN])
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
TLVReader reader;
reader.Init(buf);
err = reader.Next();
SuccessOrExit(err);
err = reader.GetBytes(macAddress, EUI48_LEN);
SuccessOrExit(err);
err = reader.Next();
SuccessOrExit(err);
err = reader.GetBytes(hmac, HMAC_BUF_LEN);
SuccessOrExit(err);
exit:
return err;
}
/**
* Null-initialize the Dropcam Legacy Pairing Server. Must call Init() prior to use.
*/
DropcamLegacyPairingServer::DropcamLegacyPairingServer(void)
{
FabricState = NULL;
ExchangeMgr = NULL;
mDelegate = NULL;
}
/**
* Initialize the Dropcam Legacy Pairing Server state and register to receive
* Dropcam Legacy Pairing messages.
*
* @param[in] exchangeMgr A pointer to the Weave Exchange Manager.
*
* @retval #WEAVE_ERROR_TOO_MANY_UNSOLICITED_MESSAGE_HANDLERS When too many unsolicited message
* handlers are registered.
* @retval #WEAVE_NO_ERROR On success.
*/
WEAVE_ERROR DropcamLegacyPairingServer::Init(WeaveExchangeManager *exchangeMgr)
{
FabricState = exchangeMgr->FabricState;
ExchangeMgr = exchangeMgr;
// Register to receive unsolicited Dropcam Legacy Pairing messages from the exchange manager.
WEAVE_ERROR err =
ExchangeMgr->RegisterUnsolicitedMessageHandler(kWeaveProfile_DropcamLegacyPairing, HandleClientRequest, this);
return err;
}
/**
* Shutdown the Dropcam Legacy Pairing Server.
*
* @retval #WEAVE_NO_ERROR unconditionally.
*/
WEAVE_ERROR DropcamLegacyPairingServer::Shutdown(void)
{
if (ExchangeMgr != NULL)
ExchangeMgr->UnregisterUnsolicitedMessageHandler(kWeaveProfile_DropcamLegacyPairing);
FabricState = NULL;
ExchangeMgr = NULL;
return WEAVE_NO_ERROR;
}
/**
* Set the delegate to process Dropcam Legacy Pairing Server events.
*
* @param[in] delegate A pointer to the Dropcam Legacy Pairing Delegate.
*/
void DropcamLegacyPairingServer::SetDelegate(DropcamLegacyPairingDelegate *delegate)
{
mDelegate = delegate;
}
void DropcamLegacyPairingServer::HandleClientRequest(ExchangeContext *ec, const IPPacketInfo *pktInfo,
const WeaveMessageInfo *msgInfo, uint32_t profileId, uint8_t msgType, PacketBuffer *msgBuf)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
DropcamLegacyPairingServer *server = reinterpret_cast<DropcamLegacyPairingServer *>(ec->AppState);
// Fail messages for the wrong profile. This shouldn't happen, but better safe than sorry.
if (profileId != kWeaveProfile_DropcamLegacyPairing)
{
WeaveServerBase::SendStatusReport(ec, kWeaveProfile_Common, Common::kStatus_BadRequest, WEAVE_NO_ERROR);
ec->Close();
ExitNow();
}
// Call on the delegate to enforce message-level access control. If policy dictates the message should NOT
// be processed, then simply end the exchange and return. If an error response was warranted, the appropriate
// response will have been sent within EnforceAccessControl().
if (!server->EnforceAccessControl(ec, profileId, msgType, msgInfo, server->mDelegate))
{
ec->Close();
ExitNow();
}
// Decode and dispatch the message.
switch (msgType)
{
case kMsgType_CameraAuthDataRequest:
err = server->HandleCameraAuthDataRequest(ec, msgBuf);
SuccessOrExit(err);
break;
default:
WeaveServerBase::SendStatusReport(ec, kWeaveProfile_Common, Common::kStatus_BadRequest, WEAVE_NO_ERROR);
ExitNow();
break;
}
exit:
if (msgBuf != NULL)
{
PacketBuffer::Free(msgBuf);
}
if (err != WEAVE_NO_ERROR)
{
WeaveLogError(DropcamLegacyPairing, "Error handling DropcamLegacyPairing client request, err = %d\n", err);
WeaveServerBase::SendStatusReport(ec, kWeaveProfile_Common, Common::kStatus_InternalError, err);
}
if (NULL != ec)
{
ec->Close();
}
}
WEAVE_ERROR DropcamLegacyPairingServer::HandleCameraAuthDataRequest(ExchangeContext *ec, PacketBuffer *(&msgBuf))
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
HMACSHA256 hmacObj;
const uint8_t authDataMessageLen = EUI48_LEN + CAMERA_NONCE_LEN;
uint8_t authDataMessage[authDataMessageLen];
uint8_t hmac[HMAC_BUF_LEN];
uint8_t macAddress[EUI48_LEN];
uint8_t secret[CAMERA_SECRET_LEN];
const uint8_t *noncePtr;
TLVReader reader;
TLVWriter writer;
VerifyOrExit(NULL != mDelegate, err = WEAVE_ERROR_INCORRECT_STATE);
memset(hmac, 0, HMAC_BUF_LEN);
// Decode request
reader.Init(msgBuf);
err = reader.Next();
SuccessOrExit(err);
VerifyOrExit(reader.GetType() == kTLVType_UTF8String, err = WEAVE_ERROR_UNEXPECTED_TLV_ELEMENT);
err = reader.GetDataPtr(noncePtr);
SuccessOrExit(err);
// Get camera MAC address
err = mDelegate->GetCameraMACAddress(macAddress);
SuccessOrExit(err);
// Get camera secret
err = mDelegate->GetCameraSecret(secret);
SuccessOrExit(err);
// Calculate HMAC of camera secret and auth_data message
memcpy(authDataMessage, macAddress, EUI48_LEN);
memcpy(&authDataMessage[EUI48_LEN], noncePtr, CAMERA_NONCE_LEN);
hmacObj.Begin(secret, CAMERA_SECRET_LEN);
hmacObj.AddData(authDataMessage, authDataMessageLen);
hmacObj.Finish(hmac);
// Re-use request buffer
PacketBuffer::Free(msgBuf);
msgBuf = PacketBuffer::New();
VerifyOrExit(msgBuf != NULL, err = WEAVE_ERROR_NO_MEMORY);
// Encode response
writer.Init(msgBuf, msgBuf->MaxDataLength());
err = writer.PutBytes(AnonymousTag, macAddress, EUI48_LEN);
SuccessOrExit(err);
err = writer.PutBytes(AnonymousTag, hmac, HMAC_BUF_LEN);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
// Send MAC address and pairing data HMAC to client
err = ec->SendMessage(kWeaveProfile_DropcamLegacyPairing, kMsgType_CameraAuthDataResponse, msgBuf, 0);
SuccessOrExit(err);
msgBuf = NULL;
exit:
return err;
}
void DropcamLegacyPairingDelegate::EnforceAccessControl(ExchangeContext *ec, uint32_t msgProfileId, uint8_t msgType,
const WeaveMessageInfo *msgInfo, AccessControlResult& result)
{
// If the result has not already been determined by a subclass...
if (result == kAccessControlResult_NotDetermined)
{
switch (msgType)
{
case kMsgType_CameraAuthDataRequest:
#if WEAVE_CONFIG_REQUIRE_AUTH_DROPCAM_LEGACY_PAIRING
if (msgInfo->PeerAuthMode == kWeaveAuthMode_PASE_PairingCode)
{
result = kAccessControlResult_Accepted;
}
#else // WEAVE_CONFIG_REQUIRE_AUTH_DROPCAM_LEGACY_PAIRING
result = kAccessControlResult_Accepted;
#endif // WEAVE_CONFIG_REQUIRE_AUTH_DROPCAM_LEGACY_PAIRING
break;
default:
WeaveServerBase::SendStatusReport(ec, kWeaveProfile_Common, Common::kStatus_UnsupportedMessage, WEAVE_NO_ERROR);
result = kAccessControlResult_Rejected_RespSent;
break;
}
}
// Call up to the base class.
WeaveServerDelegateBase::EnforceAccessControl(ec, msgProfileId, msgType, msgInfo, result);
}
} // namespace DropcamLegacyPairing
} // namespace Nestlabs
} // namespace Vendor
} // namespace Profiles
} // namespace Weave
} // namespace nl