blob: 86a98e3e7e08270882ba0a9bdf1f749563f49c2f [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* Implementation of WeaveCASEEngine class.
*
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <stdint.h>
#include <Weave/Core/WeaveCore.h>
#include "WeaveCASE.h"
#include <Weave/Core/WeaveTLV.h>
#include <Weave/Profiles/WeaveProfiles.h>
#include <Weave/Support/ASN1.h>
#include <Weave/Support/logging/WeaveLogging.h>
#include <Weave/Profiles/security/WeaveSecurity.h>
#include <Weave/Profiles/security/WeaveCert.h>
#include <Weave/Profiles/security/WeaveSig.h>
#include <Weave/Profiles/security/WeavePrivateKey.h>
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Support/crypto/HashAlgos.h>
#include <Weave/Support/crypto/EllipticCurve.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Support/WeaveFaultInjection.h>
namespace nl {
namespace Weave {
namespace Profiles {
namespace Security {
namespace CASE {
using namespace nl::Weave::Crypto;
using namespace nl::Weave::Encoding;
using namespace nl::Weave::TLV;
using namespace nl::Weave::ASN1;
#undef CASE_PRINT_CRYPTO_DATA
#ifdef CASE_PRINT_CRYPTO_DATA
static void PrintHex(const uint8_t *data, uint16_t len)
{
for (uint16_t i = 0; i < len; i++)
printf("%02X", data[i]);
}
#endif
void WeaveCASEEngine::Init()
{
memset(this, 0, sizeof(*this));
}
void WeaveCASEEngine::Shutdown()
{
ClearSecretData((uint8_t *)this, sizeof(*this));
}
void WeaveCASEEngine::Reset()
{
WeaveCASEAuthDelegate *savedAuthDelegate = AuthDelegate;
ClearSecretData((uint8_t *)this, sizeof(*this));
AuthDelegate = savedAuthDelegate;
}
void WeaveCASEEngine::SetAlternateConfigs(BeginSessionRequestContext & reqCtx)
{
uint32_t altConfig = (reqCtx.ProtocolConfig == kCASEConfig_Config1) ? kCASEConfig_Config2 : kCASEConfig_Config1;
if (IsAllowedConfig(altConfig))
{
reqCtx.AlternateConfigs[0] = altConfig;
reqCtx.AlternateConfigCount = 1;
}
}
void WeaveCASEEngine::SetAlternateCurves(BeginSessionRequestContext & reqCtx)
{
reqCtx.AlternateCurveCount = 0;
#if WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP256R1
if ((mAllowedCurves & kWeaveCurveSet_prime256v1) != 0)
reqCtx.AlternateCurveIds[reqCtx.AlternateCurveCount++] = kWeaveCurveId_prime256v1;
#endif // WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP256R1
#if WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP224R1
if ((mAllowedCurves & kWeaveCurveSet_secp224r1) != 0)
reqCtx.AlternateCurveIds[reqCtx.AlternateCurveCount++] = kWeaveCurveId_secp224r1;
#endif // WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP224R1
#if WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP192R1
if ((mAllowedCurves & kWeaveCurveSet_prime192v1) != 0)
reqCtx.AlternateCurveIds[reqCtx.AlternateCurveCount++] = kWeaveCurveId_prime192v1;
#endif // WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP192R1
#if WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP160R1
if ((mAllowedCurves & kWeaveCurveSet_secp160r1) != 0)
reqCtx.AlternateCurveIds[reqCtx.AlternateCurveCount++] = kWeaveCurveId_secp160r1;
#endif // WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP160R1
}
WEAVE_ERROR WeaveCASEEngine::GenerateBeginSessionRequest(BeginSessionRequestContext & reqCtx, PacketBuffer * msgBuf)
{
WEAVE_ERROR err;
// Verify there isn't a begin session already outstanding.
VerifyOrExit(State == kState_Idle, err = WEAVE_ERROR_INCORRECT_STATE);
// Verify the auth delegate has been set.
VerifyOrExit(AuthDelegate != NULL, err = WEAVE_ERROR_NO_CASE_AUTH_DELEGATE);
WeaveLogDetail(SecurityManager, "CASE:GenerateBeginSessionRequest");
// If a protocol config wasn't specified, choose a default one.
if (reqCtx.ProtocolConfig == CASE::kCASEConfig_NotSpecified)
reqCtx.ProtocolConfig = IsConfig2Allowed() ? kCASEConfig_Config2 : kCASEConfig_Config1;
// Verify the proposed config is supported.
VerifyOrExit(IsAllowedConfig(reqCtx.ProtocolConfig), err = WEAVE_ERROR_UNSUPPORTED_CASE_CONFIGURATION);
// If a ECDH curve wasn't specified, choose a default one.
if (reqCtx.CurveId == kWeaveCurveId_NotSpecified)
reqCtx.CurveId = WEAVE_CONFIG_DEFAULT_CASE_CURVE_ID;
// Verify the proposed ECDH curve is supported.
VerifyOrExit(IsAllowedCurve(reqCtx.CurveId), err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
// Verify the requested key type.
VerifyOrExit(WeaveKeyId::IsSessionKey(reqCtx.SessionKeyId), err = WEAVE_ERROR_WRONG_KEY_TYPE);
// Verify the requested encryption type.
VerifyOrExit(reqCtx.EncryptionType == kWeaveEncryptionType_AES128CTRSHA1,
err = WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE);
// Record that we are acting as the initiator.
SetIsInitiator(true);
reqCtx.SetIsInitiator(true);
// Remember various parameters of the session so that we can use them when the responder responds.
SetSelectedConfig(reqCtx.ProtocolConfig);
mCurveId = reqCtx.CurveId;
SetPerformingKeyConfirm(reqCtx.PerformKeyConfirm());
SessionKeyId = reqCtx.SessionKeyId;
EncryptionType = reqCtx.EncryptionType;
// Since the message contains a number of variable length fields with corresponding length values,
// we start encoding in the middle of the message, and then go back and fill in the head when we know
// the final lengths.
msgBuf->SetDataLength(reqCtx.HeadLength());
// Generate an ephemeral public/private key. Store the public key directly into the message and store
// the private key in a state variable.
err = AppendNewECDHKey(reqCtx, msgBuf);
SuccessOrExit(err);
// Append the local node's certificate information.
err = AppendCertInfo(reqCtx, msgBuf);
SuccessOrExit(err);
// Append the initiator's payload, if specified.
err = AppendPayload(reqCtx, msgBuf);
SuccessOrExit(err);
// Now encode the head of the message.
err = reqCtx.EncodeHead(msgBuf);
SuccessOrExit(err);
// Sign the message using the supplied private key and append the signature to the buffer.
// Save the message hash for later use in deriving the session keys.
err = AppendSignature(reqCtx, msgBuf, mSecureState.BeforeKeyGen.RequestMsgHash);
SuccessOrExit(err);
State = kState_BeginRequestGenerated;
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::ProcessBeginSessionRequest(PacketBuffer * msgBuf, BeginSessionRequestContext & reqCtx, ReconfigureContext & reconfCtx)
{
WEAVE_ERROR err;
bool reconfigRequired = false;
// Verify there isn't a begin session already outstanding.
VerifyOrExit(State == kState_Idle, err = WEAVE_ERROR_INCORRECT_STATE);
WeaveLogDetail(SecurityManager, "CASE:ProcessBeginSessionRequest");
// Record that we are acting as the responder.
SetIsInitiator(false);
reqCtx.SetIsInitiator(false);
// Decode the head of the message.
err = reqCtx.DecodeHead(msgBuf);
SuccessOrExit(err);
// Verify the protocol parameters proposed by the peer (protocol config and ECDH curve id). If the proposed
// values are not acceptable, but an alternative set are, setup the ReconfigureContext with the alternatives
// and return WEAVE_ERROR_CASE_RECONFIG_REQUIRED.
reconfCtx.ProtocolConfig = reqCtx.ProtocolConfig;
reconfCtx.CurveId = reqCtx.CurveId;
err = VerifyProposedConfig(reqCtx, reconfCtx.ProtocolConfig);
if (err == WEAVE_ERROR_CASE_RECONFIG_REQUIRED)
reconfigRequired = true;
else
SuccessOrExit(err);
err = VerifyProposedCurve(reqCtx, reconfCtx.CurveId);
if (err == WEAVE_ERROR_CASE_RECONFIG_REQUIRED)
reconfigRequired = true;
else
SuccessOrExit(err);
if (reconfigRequired)
ExitNow(err = WEAVE_ERROR_CASE_RECONFIG_REQUIRED);
// Remember various parameters of the session so that we can use them when we respond.
SetSelectedConfig(reqCtx.ProtocolConfig);
mCurveId = reqCtx.CurveId;
SetPerformingKeyConfirm(reqCtx.PerformKeyConfirm());
SessionKeyId = reqCtx.SessionKeyId;
EncryptionType = reqCtx.EncryptionType;
// Verify the message signature.
err = VerifySignature(reqCtx, msgBuf, mSecureState.BeforeKeyGen.RequestMsgHash);
SuccessOrExit(err);
// Verify the requested key type.
VerifyOrExit(WeaveKeyId::IsSessionKey(reqCtx.SessionKeyId), err = WEAVE_ERROR_WRONG_KEY_TYPE);
// Verify the requested encryption type.
VerifyOrExit(reqCtx.EncryptionType == kWeaveEncryptionType_AES128CTRSHA1,
err = WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE);
State = kState_BeginRequestProcessed;
exit:
if (err != WEAVE_NO_ERROR)
State = kState_Failed;
return err;
}
WEAVE_ERROR WeaveCASEEngine::GenerateBeginSessionResponse(BeginSessionResponseContext & respCtx, PacketBuffer * msgBuf,
BeginSessionRequestContext & reqCtx)
{
WEAVE_ERROR err;
uint8_t respMsgHash[kMaxHashLength];
// Verify the correct state.
VerifyOrExit(State == kState_BeginRequestProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
WeaveLogDetail(SecurityManager, "CASE:GenerateBeginSessionResponse");
respCtx.SetIsInitiator(false);
// If the initiator requested key confirmation, then we must do it.
// If the initiator DIDN'T requested key confirmation, but local policy requires it, then we force the use
// of key confirmation.
if (reqCtx.PerformKeyConfirm() || respCtx.PerformKeyConfirm() || ResponderRequiresKeyConfirm())
SetPerformingKeyConfirm(true);
// If performing key confirmation, signal so in the response and set the appropriate key confirmation
// hash length based on the selected config.
if (PerformingKeyConfirm())
{
respCtx.SetPerformKeyConfirm(true);
respCtx.KeyConfirmHashLength = ConfigHashLength();
}
// Since the message contains variable length fields with corresponding length values, start encoding the tail
// of the message, and then go back and fill in the head when we know the final lengths.
msgBuf->SetDataLength(respCtx.HeadLength());
// Generate an ephemeral public/private key. Append the public key to the message and store the private key
// in a state variable.
err = AppendNewECDHKey(respCtx, msgBuf);
SuccessOrExit(err);
// Append the local node's certificate information.
err = AppendCertInfo(respCtx, msgBuf);
SuccessOrExit(err);
// Append the responder's payload, if specified.
err = AppendPayload(respCtx, msgBuf);
SuccessOrExit(err);
// Now encode the head of the message.
err = respCtx.EncodeHead(msgBuf);
SuccessOrExit(err);
// Sign the message using the supplied private key and append the signature to the buffer.
err = AppendSignature(respCtx, msgBuf, respMsgHash);
SuccessOrExit(err);
{
uint8_t responderKeyConfirmHash[kMaxHashLength];
// Derive the session keys from the initiator's public key and our private key.
err = DeriveSessionKeys(reqCtx.ECDHPublicKey, respMsgHash, responderKeyConfirmHash);
SuccessOrExit(err);
// If performing key confirmation...
if (PerformingKeyConfirm())
{
// Append the responder hash to the BeginSessionResponse message. The initiator will use this to
// confirm that we have the correct session keys.
VerifyOrExit(msgBuf->AvailableDataLength() >= respCtx.KeyConfirmHashLength, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
uint16_t msgLen = msgBuf->DataLength();
memcpy(msgBuf->Start() + msgLen, responderKeyConfirmHash, respCtx.KeyConfirmHashLength);
msgBuf->SetDataLength(msgLen + respCtx.KeyConfirmHashLength);
State = kState_BeginResponseGenerated;
}
else
State = kState_Complete;
}
exit:
if (err != WEAVE_NO_ERROR)
State = kState_Failed;
return err;
}
WEAVE_ERROR WeaveCASEEngine::ProcessBeginSessionResponse(PacketBuffer * msgBuf, BeginSessionResponseContext & respCtx)
{
WEAVE_ERROR err;
uint8_t respMsgHash[kMaxHashLength];
VerifyOrExit(State == kState_BeginRequestGenerated, err = WEAVE_ERROR_INCORRECT_STATE);
WeaveLogDetail(SecurityManager, "CASE:ProcessBeginSessionResponse");
respCtx.SetIsInitiator(true);
// Decode the head of the message.
err = respCtx.DecodeHead(msgBuf);
SuccessOrExit(err);
// Verify the message signature.
err = VerifySignature(respCtx, msgBuf, respMsgHash);
SuccessOrExit(err);
// If we asked for key confirmation, verify the responder responded appropriately.
VerifyOrExit(!PerformingKeyConfirm() || respCtx.PerformKeyConfirm(), err = WEAVE_ERROR_INVALID_CASE_PARAMETER);
// If the responder asked for key confirmation, we have to do it.
if (respCtx.PerformKeyConfirm())
SetPerformingKeyConfirm(true);
{
uint8_t responderKeyConfirmHash[kMaxHashLength];
// Derive the session keys from the responder's public key and our private key.
err = DeriveSessionKeys(respCtx.ECDHPublicKey, respMsgHash, responderKeyConfirmHash);
SuccessOrExit(err);
// If performing key confirmation...
if (PerformingKeyConfirm())
{
uint8_t expectedKeyConfirmHashLen = ConfigHashLength();
// Check the expected responder hash against the value in the response message.
// Fail if they do not match.
WEAVE_FAULT_INJECT(nl::Weave::FaultInjection::kFault_CASEKeyConfirm, ExitNow(err = WEAVE_ERROR_KEY_CONFIRMATION_FAILED));
VerifyOrExit(respCtx.KeyConfirmHashLength == expectedKeyConfirmHashLen &&
ConstantTimeCompare(respCtx.KeyConfirmHash, responderKeyConfirmHash, expectedKeyConfirmHashLen),
err = WEAVE_ERROR_KEY_CONFIRMATION_FAILED);
State = kState_BeginResponseProcessed;
}
else
State = kState_Complete;
}
exit:
if (err != WEAVE_NO_ERROR)
State = kState_Failed;
return err;
}
WEAVE_ERROR WeaveCASEEngine::GenerateInitiatorKeyConfirm(PacketBuffer * msgBuf)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint8_t keyConfirmHashLen = ConfigHashLength();
VerifyOrExit(State == kState_BeginResponseProcessed && PerformingKeyConfirm(), err = WEAVE_ERROR_INCORRECT_STATE);
WeaveLogDetail(SecurityManager, "CASE:GenerateInitiatorKeyConfirm");
memcpy(msgBuf->Start(), mSecureState.AfterKeyGen.InitiatorKeyConfirmHash, keyConfirmHashLen);
msgBuf->SetDataLength(keyConfirmHashLen);
State = kState_Complete;
exit:
if (err != WEAVE_NO_ERROR)
State = kState_Failed;
return err;
}
WEAVE_ERROR WeaveCASEEngine::ProcessInitiatorKeyConfirm(PacketBuffer * msgBuf)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint8_t expectedKeyConfirmHashLen = ConfigHashLength();
VerifyOrExit(State == kState_BeginResponseGenerated && PerformingKeyConfirm(), err = WEAVE_ERROR_INCORRECT_STATE);
WeaveLogDetail(SecurityManager, "CASE:ProcessInitiatorKeyConfirm");
WEAVE_FAULT_INJECT(nl::Weave::FaultInjection::kFault_CASEKeyConfirm, ExitNow(err = WEAVE_ERROR_KEY_CONFIRMATION_FAILED));
VerifyOrExit(msgBuf->DataLength() == expectedKeyConfirmHashLen &&
ConstantTimeCompare(msgBuf->Start(), mSecureState.AfterKeyGen.InitiatorKeyConfirmHash, expectedKeyConfirmHashLen),
err = WEAVE_ERROR_KEY_CONFIRMATION_FAILED);
State = kState_Complete;
exit:
if (err != WEAVE_NO_ERROR)
State = kState_Failed;
return err;
}
WEAVE_ERROR WeaveCASEEngine::ProcessReconfigure(PacketBuffer * msgBuf, ReconfigureContext & reconfCtx)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveLogDetail(SecurityManager, "CASE:ProcessReconfigure");
// Decode the Reconfigure message.
err = ReconfigureContext::Decode(msgBuf, reconfCtx);
SuccessOrExit(err);
// Fail if the peer has sent more than one Reconfigure message.
VerifyOrExit(!HasReconfigured(), err = WEAVE_ERROR_TOO_MANY_CASE_RECONFIGURATIONS);
SetHasReconfigured(true);
// Verify that the peer's proposed config is allowed.
VerifyOrExit(IsAllowedConfig(reconfCtx.ProtocolConfig), err = WEAVE_ERROR_UNSUPPORTED_CASE_CONFIGURATION);
// Verify that the peer's proposed ECDH curve is allowed.
VerifyOrExit(IsAllowedCurve(reconfCtx.CurveId), err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
// Go back to Idle state so that the engine can be re-used to initiate a new CASE exchange.
State = kState_Idle;
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::GetSessionKey(const WeaveEncryptionKey *& encKey)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
VerifyOrExit(State == kState_Complete, err = WEAVE_ERROR_INCORRECT_STATE);
encKey = &mSecureState.AfterKeyGen.EncryptionKey;
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::VerifyProposedConfig(BeginSessionRequestContext & reqCtx, uint32_t & selectedAltConfig)
{
WEAVE_ERROR err = WEAVE_ERROR_UNSUPPORTED_CASE_CONFIGURATION;
WeaveLogDetail(SecurityManager, "CASE:VerifyProposedConfig");
// If Config2 is allowed, use it if it is the proposed config.
// If Config2 is NOT the proposed config, but is in the peer's list
// of alternates, force a reconfig selecting Config2.
if (IsConfig2Allowed())
{
if (reqCtx.ProtocolConfig == kCASEConfig_Config2)
ExitNow(err = WEAVE_NO_ERROR);
if (reqCtx.IsAltConfig(kCASEConfig_Config2))
{
selectedAltConfig = kCASEConfig_Config2;
ExitNow(err = WEAVE_ERROR_CASE_RECONFIG_REQUIRED);
}
}
// If Config1 is allowed, use it if it is the proposed config.
// If Config1 is NOT the proposed config, but is in the peer's list
// of alternates, force a reconfig selecting Config1.
if (IsConfig1Allowed())
{
if (reqCtx.ProtocolConfig == kCASEConfig_Config1)
ExitNow(err = WEAVE_NO_ERROR);
// If Config1 is in the peer's list of alternates, force a reconfig selecting Config1.
if (reqCtx.IsAltConfig(kCASEConfig_Config1))
{
selectedAltConfig = kCASEConfig_Config1;
ExitNow(err = WEAVE_ERROR_CASE_RECONFIG_REQUIRED);
}
}
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::VerifyProposedCurve(BeginSessionRequestContext & reqCtx, uint32_t & selectedAltCurve)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveLogDetail(SecurityManager, "CASE:VerifyProposedCurve");
// If the requested elliptic curve is not allowed, select an allowed alternate, if available.
if (!IsAllowedCurve(reqCtx.CurveId))
{
uint8_t i = 0;
for (; i < reqCtx.AlternateCurveCount; i++)
if (IsAllowedCurve(reqCtx.AlternateCurveIds[i]))
break;
VerifyOrExit(i < reqCtx.AlternateCurveCount, err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
selectedAltCurve = reqCtx.AlternateCurveIds[i];
ExitNow(err = WEAVE_ERROR_CASE_RECONFIG_REQUIRED);
}
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::AppendNewECDHKey(BeginSessionContext & msgCtx, PacketBuffer * msgBuf)
{
WEAVE_ERROR err;
EncodedECPrivateKey privKey;
uint16_t msgLen = msgBuf->DataLength();
WeaveLogDetail(SecurityManager, "CASE:AppendNewECDHKey");
// Generate an ephemeral public/private key. Store the public key directly into the message and store
// the private key in the provided object.
msgCtx.ECDHPublicKey.ECPoint = msgBuf->Start() + msgLen;
msgCtx.ECDHPublicKey.ECPointLen = msgBuf->AvailableDataLength(); // GenerateECDHKey() will update with final length.
privKey.PrivKey = mSecureState.BeforeKeyGen.ECDHPrivateKey;
privKey.PrivKeyLen = sizeof(mSecureState.BeforeKeyGen.ECDHPrivateKey);
err = GenerateECDHKey(WeaveCurveIdToOID(msgCtx.CurveId), msgCtx.ECDHPublicKey, privKey);
SuccessOrExit(err);
#if WEAVE_CONFIG_SECURITY_TEST_MODE
// If enabled, override the generated key with a known key pair to allow man-in-the-middle session key recovery for
// testing purposes.
// NOTE: We still do the work of generating a proper key pair in this case so that the execution time
// remains largely the same.
if (UseKnownECDHKey())
{
// Private key is 1.
privKey.PrivKey[0] = 0x01;
privKey.PrivKeyLen = 1;
// Public key is generator.
msgCtx.ECDHPublicKey.ECPointLen = msgBuf->AvailableDataLength();
err = GetCurveG(WeaveCurveIdToOID(msgCtx.CurveId), msgCtx.ECDHPublicKey);
SuccessOrExit(err);
WeaveLogError(SecurityManager, "WARNING: Using well-known ECDH key in CASE ***** SESSION IS NOT SECURE *****");
}
#endif // WEAVE_CONFIG_SECURITY_TEST_MODE
mSecureState.BeforeKeyGen.ECDHPrivateKeyLength = privKey.PrivKeyLen;
msgBuf->SetDataLength(msgLen + msgCtx.ECDHPublicKey.ECPointLen);
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::AppendCertInfo(BeginSessionContext & msgCtx, PacketBuffer * msgBuf)
{
WEAVE_ERROR err;
WeaveLogDetail(SecurityManager, "CASE:AppendCertInfo");
#if !WEAVE_CONFIG_LEGACY_CASE_AUTH_DELEGATE
// Initialize a TLV writer to write the CertificationInformation structure.
TLVWriter writer;
writer.Init(msgBuf);
writer.ImplicitProfileId = kWeaveProfile_Security;
// Call the auth delegate to write the certificate information for the local node.
err = AuthDelegate->EncodeNodeCertInfo(msgCtx, writer);
SuccessOrExit(err);
// Finalize the TLV encoding. Note that this updates the data length of the supplied PacketBuffer.
err = writer.Finalize();
SuccessOrExit(err);
// Capture the encoded length of the CertificateInformation structure, which will be included in
// the message header.
msgCtx.CertInfoLength = (uint16_t)writer.GetLengthWritten();
#else // !WEAVE_CONFIG_LEGACY_CASE_AUTH_DELEGATE
// Call the auth delegate to generate the certificate information for the local node and append it
// to the message.
uint16_t msgLen = msgBuf->DataLength();
err = AuthDelegate->GetNodeCertInfo(IsInitiator(), msgBuf->Start() + msgLen, msgBuf->AvailableDataLength(), msgCtx.CertInfoLength);
SuccessOrExit(err);
msgBuf->SetDataLength(msgLen + msgCtx.CertInfoLength);
#endif // WEAVE_CONFIG_LEGACY_CASE_AUTH_DELEGATE
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::AppendPayload(BeginSessionContext & msgCtx, PacketBuffer * msgBuf)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint16_t msgLen = msgBuf->DataLength();
WeaveLogDetail(SecurityManager, "CASE:AppendPayload");
err = AuthDelegate->EncodeNodePayload(msgCtx, msgBuf->Start() + msgLen, msgBuf->AvailableDataLength(), msgCtx.PayloadLength);
SuccessOrExit(err);
msgBuf->SetDataLength(msgLen + msgCtx.PayloadLength);
exit:
return err;
}
// Generate a signature for an encoded CASE message (in the supplied buffer) and append it to the message.
WEAVE_ERROR WeaveCASEEngine::AppendSignature(BeginSessionContext & msgCtx, PacketBuffer * msgBuf, uint8_t * msgHash)
{
WEAVE_ERROR err;
uint8_t * msgStart = msgBuf->Start();
uint16_t tbsDataLen = msgBuf->DataLength();
const uint64_t sigTag = ProfileTag(kWeaveProfile_Security, kTag_WeaveCASESignature);
WeaveLogDetail(SecurityManager, "CASE:AppendSignature");
// Generate a hash of the to-be-signed portion of the message.
GenerateHash(msgStart, tbsDataLen, msgHash);
// Generate a message signature using the local node's private key and append this to the message.
{
TLVWriter writer;
writer.Init(msgBuf);
writer.ImplicitProfileId = kWeaveProfile_Security;
#if !WEAVE_CONFIG_LEGACY_CASE_AUTH_DELEGATE
WeaveLogDetail(SecurityManager, "CASE:GenerateSignature");
#endif
err = AuthDelegate->GenerateNodeSignature(msgCtx, msgHash, ConfigHashLength(), writer, sigTag);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
msgCtx.Signature = msgStart + tbsDataLen;
msgCtx.SignatureLength = writer.GetLengthWritten();
}
exit:
return err;
}
// Verify a CASE message signature (for the message in the supplied buffer) against a given public key.
// Returns a hash of the signed portion of the message in the supplied bufer.
WEAVE_ERROR WeaveCASEEngine::VerifySignature(BeginSessionContext & msgCtx, PacketBuffer * msgBuf, uint8_t * msgHash)
{
WEAVE_ERROR err, validRes;
WeaveCertificateSet certSet;
ValidationContext validCtx;
WeaveDN peerCertDN;
CertificateKeyId peerCertSubjectKeyId;
EncodedECDSASignature ecdsaSig;
TLVReader reader;
uint16_t signedDataLen;
uint8_t *msgStart = msgBuf->Start();
bool callEndValidation = false;
WeaveLogDetail(SecurityManager, "CASE:VerifySignature");
// TODO: move validation to separate method
validCtx.Reset();
// Invoke the auth delegate to prepare the certificate set and the validation context.
// This will load the trust anchors into the certificate set and establish the
// desired validation criteria for the peer's entity certificate.
err = AuthDelegate->BeginValidation(msgCtx, validCtx, certSet);
SuccessOrExit(err);
callEndValidation = true;
// If the cert type property has been set, set it as the required certificate type in the
// validation context such that the cert type is enforced during the call to FindValidCert().
validCtx.RequiredCertType = mCertType;
WeaveLogDetail(SecurityManager, "CASE:DecodeCertificateInfo");
// Decode the certificate information supplied by the peer.
validCtx.RequiredKeyUsages |= kKeyUsageFlag_DigitalSignature;
err = DecodeCertificateInfo(msgCtx, certSet, peerCertDN, peerCertSubjectKeyId);
if (err == WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE || err == WEAVE_ERROR_UNSUPPORTED_SIGNATURE_TYPE)
{
err = WEAVE_ERROR_UNSUPPORTED_CERT_FORMAT;
}
SuccessOrExit(err);
// Allow the auth delegate to inspect and possibly update the peer's subject information and/or
// the set of certificates that will be used for validation.
err = AuthDelegate->OnPeerCertsLoaded(msgCtx, peerCertDN, peerCertSubjectKeyId, validCtx, certSet);
SuccessOrExit(err);
// Search the certificate set for the peer's certificate and validate that it is trusted
// and suitable for CASE authentication. This method performs certificate chain construction
// and validates that there is a path from the peer's certificate to a trust anchor certificate.
WeaveLogDetail(SecurityManager, "CASE:ValidateCert");
validRes = certSet.FindValidCert(peerCertDN, peerCertSubjectKeyId, validCtx, validCtx.SigningCert);
// If a valid certificate was found for the peer capture the type of the peer's certificate.
if (validRes == WEAVE_NO_ERROR)
{
// Update the cert type property with the type of the peer's certificate.
mCertType = validCtx.SigningCert->CertType;
}
// Tell the auth delegate about the results of certificate validation and let it weigh in
// on the process. Note that this may alter the validation result (validRes) from successful
// to unsuccessful.
err = AuthDelegate->HandleValidationResult(msgCtx, validCtx, certSet, validRes);
// TODO: verify that validRes not reset to NO_ERROR.
SuccessOrExit(err);
// Check that validation succeeded.
VerifyOrExit(validRes == WEAVE_NO_ERROR, err = validRes);
VerifyOrExit(validCtx.SigningCert != NULL, err = WEAVE_ERROR_INCORRECT_STATE);
// Decode the CASE signature from the end of the message.
reader.Init(msgCtx.Signature, msgCtx.SignatureLength);
reader.ImplicitProfileId = kWeaveProfile_Security;
err = reader.Next(kTLVType_Structure, ProfileTag(kWeaveProfile_Security, kTag_WeaveCASESignature));
SuccessOrExit(err);
WeaveLogDetail(SecurityManager, "CASE:DecodeWeaveECDSASignature");
err = DecodeWeaveECDSASignature(reader, ecdsaSig);
SuccessOrExit(err);
err = reader.VerifyEndOfContainer();
SuccessOrExit(err);
// Compute the length of the signed portion of the message.
signedDataLen = msgCtx.Signature - msgStart;
// Generate a hash of the to-be-signed portion of the message.
GenerateHash(msgStart, signedDataLen, msgHash);
// Verify the message signature against the computed message hash and the
// the public key from the peer's certificate.
WeaveLogDetail(SecurityManager, "CASE:VerifyECDSASignature");
err = VerifyECDSASignature(WeaveCurveIdToOID(validCtx.SigningCert->PubKeyCurveId), msgHash, ConfigHashLength(),
ecdsaSig, validCtx.SigningCert->PublicKey.EC);
SuccessOrExit(err);
exit:
if (callEndValidation)
{
AuthDelegate->EndValidation(msgCtx, validCtx, certSet);
}
certSet.Release();
return err;
}
WEAVE_ERROR WeaveCASEEngine::DecodeCertificateInfo(BeginSessionContext & msgCtx, WeaveCertificateSet & certSet,
WeaveDN & entityCertDN, CertificateKeyId & entityCertSubjectKeyId)
{
WEAVE_ERROR err;
TLVReader reader;
WeaveCertificateData *entityCert = NULL;
// Begin decoding the certificate information structure.
reader.Init(msgCtx.CertInfo, msgCtx.CertInfoLength);
reader.ImplicitProfileId = kWeaveProfile_Security;
err = reader.Next(kTLVType_Structure, ProfileTag(kWeaveProfile_Security, kTag_WeaveCASECertificateInformation));
SuccessOrExit(err);
{
TLVType containerType;
err = reader.EnterContainer(containerType);
SuccessOrExit(err);
err = reader.Next();
// Look for the element representing the certificate of the authenticating entity (i.e. the peer). If found...
if (err == WEAVE_NO_ERROR && reader.GetTag() == ContextTag(kTag_CASECertificateInfo_EntityCertificate))
{
// Load the authenticating entity's certificate into the certificate set.
err = certSet.LoadCert(reader, kDecodeFlag_GenerateTBSHash, entityCert);
SuccessOrExit(err);
entityCertDN = entityCert->SubjectDN;
entityCertSubjectKeyId = entityCert->SubjectKeyId;
err = reader.Next();
}
// Look for the EntityCertificateRef element and fail if found.
// NOTE: This version of the code does not support the use of certificate reference to identify the certificate.
if (err == WEAVE_NO_ERROR && reader.GetTag() == ContextTag(kTag_CASECertificateInfo_EntityCertificateRef))
{
ExitNow(err = WEAVE_ERROR_UNSUPPORTED_CERT_FORMAT); // TODO: use better error
}
// Look for the RelatedCertificates element. If found, load the contained certificates into the certificate set.
if (err == WEAVE_NO_ERROR && reader.GetTag() == ContextTag(kTag_CASECertificateInfo_RelatedCertificates))
{
err = certSet.LoadCerts(reader, kDecodeFlag_GenerateTBSHash);
SuccessOrExit(err);
err = reader.Next();
}
// Skip the TrustAnchors element if present. This represents information an initiator provides to the
// responder about what certificates it trusts, allowing the responder to select an appropriate entity
// certificate to respond with. This code assumes that the local node only has a single entity certificate,
// and thus it's that or nothing.
if (err == WEAVE_NO_ERROR && reader.GetTag() == ContextTag(kTag_CASECertificateInfo_TrustAnchors))
{
err = reader.Next();
}
if (err == WEAVE_NO_ERROR)
ExitNow(err = WEAVE_ERROR_UNEXPECTED_TLV_ELEMENT);
if (err != WEAVE_END_OF_TLV)
ExitNow();
err = reader.ExitContainer(containerType);
SuccessOrExit(err);
}
err = reader.VerifyEndOfContainer();
SuccessOrExit(err);
// Verify that the peer included its entity certificate.
VerifyOrExit(entityCert != NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::DeriveSessionKeys(EncodedECPublicKey & pubKey, const uint8_t * respMsgHash,
uint8_t * responderKeyConfirmHash)
{
WEAVE_ERROR err;
uint8_t hashLen = ConfigHashLength();
#if WEAVE_CONFIG_SUPPORT_CASE_CONFIG1
HKDFSHA1Or256 hkdf(IsUsingConfig1());
#else
HKDFSHA256 hkdf;
#endif
WeaveLogDetail(SecurityManager, "CASE:DeriveSessionKeys");
// Only AES128CTRSHA1 keys supported for now.
VerifyOrExit(EncryptionType == kWeaveEncryptionType_AES128CTRSHA1, err = WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE);
// Prepare a salt value to be used in the generation of the master key. The salt value
// is composed from the hashes of the signed portions of the CASE request and response
// messages. Thus the salt incorporates the entity certificates for both parties plus
// their ephemeral DH public keys.
{
uint8_t keySalt[2 * kMaxHashLength];
memcpy(keySalt, mSecureState.BeforeKeyGen.RequestMsgHash, hashLen);
memcpy(keySalt + hashLen, respMsgHash, hashLen);
#ifdef CASE_PRINT_CRYPTO_DATA
printf("Salt: "); PrintHex(keySalt, 2 * hashLen); printf("\n");
#endif
hkdf.BeginExtractKey(keySalt, 2 * hashLen);
}
// Generate a master key from which the session keys will be derived...
{
EncodedECPrivateKey privKey;
uint8_t sharedSecretBuf[kMaxECDHSharedSecretSize];
uint16_t sharedSecretLen;
OID curveOID;
// Compute the Diffie-Hellman shared secret from the peer's public key and our private key.
privKey.PrivKey = mSecureState.BeforeKeyGen.ECDHPrivateKey;
privKey.PrivKeyLen = mSecureState.BeforeKeyGen.ECDHPrivateKeyLength;
curveOID = WeaveCurveIdToOID(mCurveId);
err = ECDHComputeSharedSecret(curveOID, pubKey, privKey, sharedSecretBuf, sizeof(sharedSecretBuf), sharedSecretLen);
SuccessOrExit(err);
ClearSecretData(mSecureState.BeforeKeyGen.ECDHPrivateKey, sizeof(mSecureState.BeforeKeyGen.ECDHPrivateKey));
#ifdef CASE_PRINT_CRYPTO_DATA
printf("Key Material: "); PrintHex(sharedSecretBuf, sharedSecretLen); printf("\n");
#endif
// Perform HKDF-based key extraction to produce a master pseudo-random key from the
// ECDH shared secret.
hkdf.AddKeyMaterial(sharedSecretBuf, sharedSecretLen);
err = hkdf.FinishExtractKey();
ClearSecretData(sharedSecretBuf, sizeof(sharedSecretBuf));
SuccessOrExit(err);
}
// Derive the session keys from the master key...
{
uint8_t sessionKeyData[WeaveEncryptionKey_AES128CTRSHA1::KeySize + kMaxHashLength];
uint16_t keyLen;
// If performing key confirmation, arrange to generate enough key data for the session
// keys (data encryption and integrity) as well as a key to be used in key confirmation.
if (PerformingKeyConfirm())
keyLen = WeaveEncryptionKey_AES128CTRSHA1::KeySize + hashLen;
else
keyLen = WeaveEncryptionKey_AES128CTRSHA1::KeySize;
// Perform HKDF-based key expansion to produce the desired key data.
err = hkdf.ExpandKey(NULL, 0, keyLen, sessionKeyData);
SuccessOrExit(err);
#ifdef CASE_PRINT_CRYPTO_DATA
printf("Session Key Data: "); PrintHex(sessionKeyData, keyLen); printf("\n");
#endif
// Copy the generated key data to the appropriate destinations.
memcpy(mSecureState.AfterKeyGen.EncryptionKey.AES128CTRSHA1.DataKey,
sessionKeyData,
WeaveEncryptionKey_AES128CTRSHA1::DataKeySize);
memcpy(mSecureState.AfterKeyGen.EncryptionKey.AES128CTRSHA1.IntegrityKey,
sessionKeyData + WeaveEncryptionKey_AES128CTRSHA1::DataKeySize,
WeaveEncryptionKey_AES128CTRSHA1::IntegrityKeySize);
// If performing key confirmation...
if (PerformingKeyConfirm())
{
// Use the key confirmation key to generate key confirmation hashes. Store the initiator hash
// (the single hash) in state data for later use. Return the responder hash (the double hash)
// to the caller.
uint8_t *keyConfirmKey = sessionKeyData + WeaveEncryptionKey_AES128CTRSHA1::KeySize;
GenerateKeyConfirmHashes(keyConfirmKey, mSecureState.AfterKeyGen.InitiatorKeyConfirmHash,
responderKeyConfirmHash);
}
ClearSecretData(sessionKeyData, sizeof(sessionKeyData));
}
exit:
return err;
}
void WeaveCASEEngine::GenerateHash(const uint8_t * inData, uint16_t inDataLen, uint8_t * hash)
{
if (IsUsingConfig1())
{
SHA1 sha;
sha.Begin();
sha.AddData(inData, inDataLen);
sha.Finish(hash);
}
else
{
SHA256 sha;
sha.Begin();
sha.AddData(inData, inDataLen);
sha.Finish(hash);
}
}
void WeaveCASEEngine::GenerateKeyConfirmHashes(const uint8_t * keyConfirmKey, uint8_t * singleHash, uint8_t * doubleHash)
{
uint8_t hashLen = ConfigHashLength();
// Generate a single hash of the key confirmation key.
GenerateHash(keyConfirmKey, hashLen, singleHash);
// Generate a double hash of the key confirmation key.
GenerateHash(singleHash, hashLen, doubleHash);
}
uint32_t WeaveCASEEngine::SelectedConfig() const
{
if (IsUsingConfig1())
return kCASEConfig_Config1;
else
return kCASEConfig_Config2;
}
bool WeaveCASEEngine::IsAllowedConfig(uint32_t config) const
{
return (config == kCASEConfig_Config1 && IsConfig1Allowed()) ||
(config == kCASEConfig_Config2 && IsConfig2Allowed());
}
#if !WEAVE_CONFIG_LEGACY_CASE_AUTH_DELEGATE
WEAVE_ERROR WeaveCASEAuthDelegate::EncodeNodePayload(const BeginSessionContext & msgCtx,
uint8_t * payloadBuf, uint16_t payloadBufSize, uint16_t & payloadLen)
{
payloadLen = 0;
return WEAVE_NO_ERROR;
}
WEAVE_ERROR WeaveCASEAuthDelegate::OnPeerCertsLoaded(const BeginSessionContext & msgCtx,
WeaveDN & subjectDN, CertificateKeyId & subjectKeyId, ValidationContext & validCtx,
WeaveCertificateSet & certSet)
{
return WEAVE_NO_ERROR;
}
#else // !WEAVE_CONFIG_LEGACY_CASE_AUTH_DELEGATE
WEAVE_ERROR WeaveCASEAuthDelegate::GenerateNodeSignature(const BeginSessionContext & msgCtx,
const uint8_t * msgHash, uint8_t msgHashLen, TLVWriter & writer, uint64_t tag)
{
WEAVE_ERROR err;
const uint8_t * signingKey = NULL;
uint16_t signingKeyLen;
WeaveLogDetail(SecurityManager, "CASE:GetNodePrivateKey");
// Get the local node's private key.
err = GetNodePrivateKey(msgCtx.IsInitiator(), signingKey, signingKeyLen);
SuccessOrExit(err);
WeaveLogDetail(SecurityManager, "CASE:GenerateSignature");
// Generate a Weave ECDSA signature and append it to the message.
err = GenerateAndEncodeWeaveECDSASignature(writer, tag, msgHash, msgHashLen, signingKey, signingKeyLen);
SuccessOrExit(err);
exit:
// Release the node private key.
if (signingKey != NULL)
{
WEAVE_ERROR relErr = ReleaseNodePrivateKey(signingKey);
err = (err == WEAVE_NO_ERROR) ? relErr : err;
}
return err;
}
#endif // WEAVE_CONFIG_LEGACY_CASE_AUTH_DELEGATE
/**
* Encodes a WeaveCASECertificateInformation TLV structure
*
* This method encodes a WeaveCASECertificateInformation structure in Weave TLV form
* containing a specified entity certificate and an optional intermediate certificate.
* The resultant CASE certificate info structure is written to a supplied buffer.
*
* @param[in] buf The buffer into which the encoded CASE certificate info
* structure should be written.
* @param[in] bufSize The size in bytes of the buffer pointed at by buf.
* @param[out] certInfoLen An integer value that will receive the final encoded size
* of the CASE certificate info structure. This value is
* only meaningful in the event that the function succeeds.
* @param[in] entityCert A buffer containing the entity certificate to be included
* in the CASE certificate info structure. The entity
* certificate is expected to be encoded in Weave TLV form.
* @param[in] entityCertLen The length in bytes of the encoded entity certificate.
* @param[in] intermediateCert Optionally, an buffer containing an intermediate certificate
* to be included as a related certificate within the CASE
* certificate info structure. When supplied, the intermediate
* certificate is expected to be encoded in Weave TLV form.
* If NULL is given, the generated certificate info structure
* will not contain any related certificates.
* @param[in] intermediateCertLen The length in bytes of the encoded intermediate certificate.
*
* @retval #WEAVE_NO_ERROR If the operation succeeded.
* @retval other Other Weave error codes related to the decoding of the
* input certificates or the encoding of the CASE certificate
* info structure.
*/
WEAVE_ERROR EncodeCASECertInfo(uint8_t * buf, uint16_t bufSize, uint16_t& certInfoLen,
const uint8_t * entityCert, uint16_t entityCertLen,
const uint8_t * intermediateCert, uint16_t intermediateCertLen)
{
WEAVE_ERROR err;
TLVWriter writer;
writer.Init(buf, bufSize);
writer.ImplicitProfileId = kWeaveProfile_Security;
err = EncodeCASECertInfo(writer, entityCert, entityCertLen, intermediateCert, intermediateCertLen);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
certInfoLen = writer.GetLengthWritten();
exit:
return err;
}
/**
* Encodes a WeaveCASECertificateInformation TLV structure
*
* This method encodes a WeaveCASECertificateInformation structure in Weave TLV form
* containing a specified entity certificate and an optional intermediate certificate.
* The resultant CASE certificate info structure is written to a supplied TLVWriter.
*
* @param[in] writer The TLVWriter object to which the encoded CASE certificate
* info structure should be written.
* @param[in] entityCert A buffer containing the entity certificate to be included
* in the CASE certificate info structure. The entity
* certificate is expected to be encoded in Weave TLV form.
* @param[in] entityCertLen The length in bytes of the encoded entity certificate.
* @param[in] intermediateCert Optionally, an buffer containing an intermediate certificate
* to be included as a related certificate within the CASE
* certificate info structure. When supplied, the intermediate
* certificate is expected to be encoded in Weave TLV form.
* If NULL is given, the generated certificate info structure
* will not contain any related certificates.
* @param[in] intermediateCertLen The length in bytes of the encoded intermediate certificate.
*
* @retval #WEAVE_NO_ERROR If the operation succeeded.
* @retval other Other Weave error codes related to the decoding of the
* input certificates or the encoding of the CASE certificate
* info structure.
*/
WEAVE_ERROR EncodeCASECertInfo(TLVWriter & writer,
const uint8_t * entityCert, uint16_t entityCertLen,
const uint8_t * intermediateCert, uint16_t intermediateCertLen)
{
WEAVE_ERROR err;
TLVType container;
// Start the WeaveCASECertificateInformation structure.
err = writer.StartContainer(ProfileTag(kWeaveProfile_Security, kTag_WeaveCASECertificateInformation), kTLVType_Structure, container);
SuccessOrExit(err);
// Copy the supplied entity certificate into CASE certificate info structure using the EntityCertificate tag.
err = writer.CopyContainer(ContextTag(kTag_CASECertificateInfo_EntityCertificate), entityCert, entityCertLen);
SuccessOrExit(err);
// If an intermediate certificate has been supplied...
if (intermediateCert != NULL)
{
TLVType container2;
// Start the list of RelatedCertificates with the CASE certificate info structure.
err = writer.StartContainer(ContextTag(kTag_CASECertificateInfo_RelatedCertificates), kTLVType_Path, container2);
SuccessOrExit(err);
// Copy the supplied intermediate certificate into the RelatedCertificates list.
err = writer.CopyContainer(ProfileTag(kWeaveProfile_Security, kTag_WeaveCertificate), intermediateCert, intermediateCertLen);
SuccessOrExit(err);
// End the RelatedCertificates list.
err = writer.EndContainer(container2);
SuccessOrExit(err);
}
// End the CASE certificate info structure.
err = writer.EndContainer(container);
SuccessOrExit(err);
exit:
return err;
}
} // namespace CASE
} // namespace Security
} // namespace Profiles
} // namespace Weave
} // namespace nl