blob: a3e1ce6a1a1581849f8284fd9e495962a97ca156 [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(BeginSessionRequestMessage& req)
{
uint32_t altConfig = (req.ProtocolConfig == kCASEConfig_Config1) ? kCASEConfig_Config2 : kCASEConfig_Config1;
if (IsAllowedConfig(altConfig))
{
req.AlternateConfigs[0] = altConfig;
req.AlternateConfigCount = 1;
}
}
void WeaveCASEEngine::SetAlternateCurves(BeginSessionRequestMessage& req)
{
req.AlternateCurveCount = 0;
#if WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP256R1
if ((mAllowedCurves & kWeaveCurveSet_prime256v1) != 0)
req.AlternateCurveIds[req.AlternateCurveCount++] = kWeaveCurveId_prime256v1;
#endif // WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP256R1
#if WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP224R1
if ((mAllowedCurves & kWeaveCurveSet_secp224r1) != 0)
req.AlternateCurveIds[req.AlternateCurveCount++] = kWeaveCurveId_secp224r1;
#endif // WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP224R1
#if WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP192R1
if ((mAllowedCurves & kWeaveCurveSet_prime192v1) != 0)
req.AlternateCurveIds[req.AlternateCurveCount++] = kWeaveCurveId_prime192v1;
#endif // WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP192R1
#if WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP160R1
if ((mAllowedCurves & kWeaveCurveSet_secp160r1) != 0)
req.AlternateCurveIds[req.AlternateCurveCount++] = kWeaveCurveId_secp160r1;
#endif // WEAVE_CONFIG_SUPPORT_ELLIPTIC_CURVE_SECP160R1
}
WEAVE_ERROR WeaveCASEEngine::GenerateBeginSessionRequest(BeginSessionRequestMessage& req, 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 (req.ProtocolConfig == CASE::kCASEConfig_NotSpecified)
req.ProtocolConfig = IsConfig2Allowed() ? kCASEConfig_Config2 : kCASEConfig_Config1;
// Verify the proposed config is supported.
VerifyOrExit(IsAllowedConfig(req.ProtocolConfig), err = WEAVE_ERROR_UNSUPPORTED_CASE_CONFIGURATION);
// If a ECDH curve wasn't specified, choose a default one.
if (req.CurveId == kWeaveCurveId_NotSpecified)
req.CurveId = WEAVE_CONFIG_DEFAULT_CASE_CURVE_ID;
// Verify the proposed ECDH curve is supported.
VerifyOrExit(IsAllowedCurve(req.CurveId), err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
// Verify the requested key type.
VerifyOrExit(WeaveKeyId::IsSessionKey(req.SessionKeyId), err = WEAVE_ERROR_WRONG_KEY_TYPE);
// Verify the requested encryption type.
VerifyOrExit(req.EncryptionType == kWeaveEncryptionType_AES128CTRSHA1,
err = WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE);
SetIsInitiator(true);
// Remember various parameters of the session so that we can use them when the responder responds.
SetSelectedConfig(req.ProtocolConfig);
mCurveId = req.CurveId;
SetPerformingKeyConfirm(req.PerformKeyConfirm);
SessionKeyId = req.SessionKeyId;
EncryptionType = req.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(req.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(req, msgBuf);
SuccessOrExit(err);
// Append the local node's certificate information.
err = AppendCertInfo(req, msgBuf);
SuccessOrExit(err);
// Append the initiator's payload, if specified.
err = AppendPayload(req, msgBuf);
SuccessOrExit(err);
// Now encode the head of the message.
err = req.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(req, msgBuf, mSecureState.BeforeKeyGen.RequestMsgHash);
SuccessOrExit(err);
State = kState_BeginRequestGenerated;
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::ProcessBeginSessionRequest(PacketBuffer *msgBuf, BeginSessionRequestMessage& req, ReconfigureMessage& reconf)
{
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);
// Decode the head of the message.
err = req.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 ReconfigureMessage with the alternatives
// and return WEAVE_ERROR_CASE_RECONFIG_REQUIRED.
reconf.ProtocolConfig = req.ProtocolConfig;
reconf.CurveId = req.CurveId;
err = VerifyProposedConfig(req, reconf.ProtocolConfig);
if (err == WEAVE_ERROR_CASE_RECONFIG_REQUIRED)
reconfigRequired = true;
else
SuccessOrExit(err);
err = VerifyProposedCurve(req, reconf.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(req.ProtocolConfig);
mCurveId = req.CurveId;
SetPerformingKeyConfirm(req.PerformKeyConfirm);
SessionKeyId = req.SessionKeyId;
EncryptionType = req.EncryptionType;
// Verify the message signature.
err = VerifySignature(req, msgBuf, mSecureState.BeforeKeyGen.RequestMsgHash);
SuccessOrExit(err);
// Verify the requested key type.
VerifyOrExit(WeaveKeyId::IsSessionKey(req.SessionKeyId), err = WEAVE_ERROR_WRONG_KEY_TYPE);
// Verify the requested encryption type.
VerifyOrExit(req.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(BeginSessionResponseMessage& resp, PacketBuffer *msgBuf,
BeginSessionRequestMessage& req)
{
WEAVE_ERROR err;
uint8_t respMsgHash[kMaxHashLength];
// Verify the correct state.
VerifyOrExit(State == kState_BeginRequestProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
WeaveLogDetail(SecurityManager, "CASE:GenerateBeginSessionResponse");
// If the initiator requested key confirmation, then we must do it.
// If the initiator DIDN'T requested key confirmation, but local policy requires it, the we force the use
// of key confirmation.
if (req.PerformKeyConfirm || resp.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())
{
resp.PerformKeyConfirm = true;
resp.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(resp.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(resp, msgBuf);
SuccessOrExit(err);
// Append the local node's certificate information.
err = AppendCertInfo(resp, msgBuf);
SuccessOrExit(err);
// Append the responder's payload, if specified.
err = AppendPayload(resp, msgBuf);
SuccessOrExit(err);
// Now encode the head of the message.
err = resp.EncodeHead(msgBuf);
SuccessOrExit(err);
// Sign the message using the supplied private key and append the signature to the buffer.
err = AppendSignature(resp, msgBuf, respMsgHash);
SuccessOrExit(err);
{
uint8_t responderKeyConfirmHash[kMaxHashLength];
// Derive the session keys from the initiator's public key and our private key.
err = DeriveSessionKeys(req.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() >= resp.KeyConfirmHashLength, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
uint16_t msgLen = msgBuf->DataLength();
memcpy(msgBuf->Start() + msgLen, responderKeyConfirmHash, resp.KeyConfirmHashLength);
msgBuf->SetDataLength(msgLen + resp.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, BeginSessionResponseMessage& resp)
{
WEAVE_ERROR err;
uint8_t respMsgHash[kMaxHashLength];
VerifyOrExit(State == kState_BeginRequestGenerated, err = WEAVE_ERROR_INCORRECT_STATE);
WeaveLogDetail(SecurityManager, "CASE:ProcessBeginSessionResponse");
// Decode the head of the message.
err = resp.DecodeHead(msgBuf);
SuccessOrExit(err);
// Verify the message signature.
err = VerifySignature(resp, msgBuf, respMsgHash);
SuccessOrExit(err);
// If we asked for key confirmation, verify the responder responded appropriately.
VerifyOrExit(!PerformingKeyConfirm() || resp.PerformKeyConfirm, err = WEAVE_ERROR_INVALID_CASE_PARAMETER);
// If the responder asked for key confirmation, we have to do it.
if (resp.PerformKeyConfirm)
SetPerformingKeyConfirm(true);
{
uint8_t responderKeyConfirmHash[kMaxHashLength];
// Derive the session keys from the responder's public key and our private key.
err = DeriveSessionKeys(resp.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(resp.KeyConfirmHashLength == expectedKeyConfirmHashLen &&
ConstantTimeCompare(resp.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);
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();
WeaveLogDetail(SecurityManager, "CASE:ProcessInitiatorKeyConfirm");
VerifyOrExit(State == kState_BeginResponseGenerated && PerformingKeyConfirm(), err = WEAVE_ERROR_INCORRECT_STATE);
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, ReconfigureMessage& reconf)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveLogDetail(SecurityManager, "CASE:ProcessReconfigure");
// Decode the Reconfigure message.
err = ReconfigureMessage::Decode(msgBuf, reconf);
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(reconf.ProtocolConfig), err = WEAVE_ERROR_UNSUPPORTED_CASE_CONFIGURATION);
// Verify that the peer's proposed ECDH curve is allowed.
VerifyOrExit(IsAllowedCurve(reconf.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(kState_Complete, err = WEAVE_ERROR_INCORRECT_STATE);
encKey = &mSecureState.AfterKeyGen.EncryptionKey;
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::VerifyProposedConfig(BeginSessionRequestMessage& req, 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 (req.ProtocolConfig == kCASEConfig_Config2)
ExitNow(err = WEAVE_NO_ERROR);
if (req.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 (req.ProtocolConfig == kCASEConfig_Config1)
ExitNow(err = WEAVE_NO_ERROR);
// If Config1 is in the peer's list of alternates, force a reconfig selecting Config1.
if (req.IsAltConfig(kCASEConfig_Config1))
{
selectedAltConfig = kCASEConfig_Config1;
ExitNow(err = WEAVE_ERROR_CASE_RECONFIG_REQUIRED);
}
}
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::VerifyProposedCurve(BeginSessionRequestMessage& req, 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(req.CurveId))
{
uint8_t i = 0;
for (; i < req.AlternateCurveCount; i++)
if (IsAllowedCurve(req.AlternateCurveIds[i]))
break;
VerifyOrExit(i < req.AlternateCurveCount, err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
selectedAltCurve = req.AlternateCurveIds[i];
ExitNow(err = WEAVE_ERROR_CASE_RECONFIG_REQUIRED);
}
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::AppendNewECDHKey(BeginSessionMessageBase& msg, 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.
msg.ECDHPublicKey.ECPoint = msgBuf->Start() + msgLen;
msg.ECDHPublicKey.ECPointLen = msgBuf->AvailableDataLength(); // GenerateECDHKey() will update with final length.
privKey.PrivKey = mSecureState.BeforeKeyGen.ECDHPrivateKey;
privKey.PrivKeyLen = sizeof(mSecureState.BeforeKeyGen.ECDHPrivateKey);
err = GenerateECDHKey(WeaveCurveIdToOID(msg.CurveId), msg.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.
msg.ECDHPublicKey.ECPointLen = msgBuf->AvailableDataLength();
err = GetCurveG(WeaveCurveIdToOID(msg.CurveId), msg.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 + msg.ECDHPublicKey.ECPointLen);
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::AppendCertInfo(BeginSessionMessageBase& msg, PacketBuffer *msgBuf)
{
WEAVE_ERROR err;
uint16_t msgLen = msgBuf->DataLength();
WeaveLogDetail(SecurityManager, "CASE:AppendCertInfo");
// Using the auth delegate, generate the certificate information for the local node and append it to the message.
err = AuthDelegate->GetNodeCertInfo(IsInitiator(), msgBuf->Start() + msgLen, msgBuf->AvailableDataLength(), msg.CertInfoLength);
SuccessOrExit(err);
msgBuf->SetDataLength(msgLen + msg.CertInfoLength);
exit:
return err;
}
WEAVE_ERROR WeaveCASEEngine::AppendPayload(BeginSessionMessageBase& msg, PacketBuffer *msgBuf)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint16_t msgLen = msgBuf->DataLength();
WeaveLogDetail(SecurityManager, "CASE:AppendPayload");
err = AuthDelegate->GetNodePayload(IsInitiator(), msgBuf->Start() + msgLen, msgBuf->AvailableDataLength(), msg.PayloadLength);
SuccessOrExit(err);
msgBuf->SetDataLength(msgLen + msg.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(BeginSessionMessageBase& msg, PacketBuffer *msgBuf, uint8_t *msgHash)
{
WEAVE_ERROR err;
uint32_t privKeyCurveId;
EncodedECPublicKey pubKey;
EncodedECPrivateKey privKey;
EncodedECDSASignature ecdsaSig;
uint8_t ecdsaRBuf[EncodedECDSASignature::kMaxValueLength];
uint8_t ecdsaSBuf[EncodedECDSASignature::kMaxValueLength];
uint8_t *msgStart = msgBuf->Start();
uint16_t signedDataLen = msgBuf->DataLength();
const uint8_t *signingKey;
uint16_t signingKeyLen;
WeaveLogDetail(SecurityManager, "CASE:AppendSignature");
// Generate a hash of the signed portion of the message.
GenerateHash(msgStart, signedDataLen, msgHash);
WeaveLogDetail(SecurityManager, "CASE:GetNodePrivateKey");
// Get the private key to sign the message.
err = AuthDelegate->GetNodePrivateKey(IsInitiator(), signingKey, signingKeyLen);
SuccessOrExit(err);
// Decode the supplied private key.
err = DecodeWeaveECPrivateKey(signingKey, signingKeyLen, privKeyCurveId, pubKey, privKey);
SuccessOrExit(err);
// Use temporary buffers to hold the generated signature value until we write it to the end of the message.
ecdsaSig.R = ecdsaRBuf;
ecdsaSig.RLen = sizeof(ecdsaRBuf);
ecdsaSig.S = ecdsaSBuf;
ecdsaSig.SLen = sizeof(ecdsaSBuf);
WeaveLogDetail(SecurityManager, "CASE:GenerateECDSASignature");
// Generate the signature for the message based on its hash.
err = GenerateECDSASignature(WeaveCurveIdToOID(privKeyCurveId), msgHash, ConfigHashLength(), privKey, ecdsaSig);
SuccessOrExit(err);
err = AuthDelegate->ReleaseNodePrivateKey(signingKey);
SuccessOrExit(err);
// Append a CASE signature object to the end of the message buffer.
{
TLVWriter writer;
writer.Init(msgBuf);
writer.ImplicitProfileId = kWeaveProfile_Security;
WeaveLogDetail(SecurityManager, "CASE:EncodeWeaveECDSASignature");
err = EncodeWeaveECDSASignature(writer, ecdsaSig, ProfileTag(kWeaveProfile_Security, kTag_WeaveCASESignature));
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
msg.Signature = msgStart + signedDataLen;
msg.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(BeginSessionMessageBase& msg, PacketBuffer *msgBuf, uint8_t *msgHash)
{
WEAVE_ERROR err, validRes;
WeaveCertificateSet certSet;
ValidationContext certValidContext;
WeaveDN peerCertDN;
CertificateKeyId peerCertSubjectKeyId;
WeaveCertificateData *peerCert = NULL;
EncodedECDSASignature ecdsaSig;
TLVReader reader;
uint16_t signedDataLen;
uint8_t *msgStart = msgBuf->Start();
bool callEndCertValidation = false;
WeaveLogDetail(SecurityManager, "CASE:VerifySignature");
// 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->BeginCertValidation(IsInitiator(), certSet, certValidContext);
SuccessOrExit(err);
callEndCertValidation = 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().
certValidContext.RequiredCertType = mCertType;
WeaveLogDetail(SecurityManager, "CASE:DecodeCertificateInfo");
// Decode the certificate information supplied by the peer.
certValidContext.RequiredKeyUsages |= kKeyUsageFlag_DigitalSignature;
validRes = DecodeCertificateInfo(msg, certSet, peerCertDN, peerCertSubjectKeyId);
// If decoding was successful, search the certificate set for the peer's certificate and
// validate that it is trusted and suitable for CASE authentication.
if (validRes == WEAVE_NO_ERROR)
{
WeaveLogDetail(SecurityManager, "CASE:ValidateCert");
validRes = certSet.FindValidCert(peerCertDN, peerCertSubjectKeyId, certValidContext, peerCert);
}
// If a valid certificate was found for the peer...
if (validRes == WEAVE_NO_ERROR)
{
// Update the cert type property with the type of the peer's certificate.
mCertType = peerCert->CertType;
}
// Tell the auth delegate about the results of basic certificate validation and let it weigh in
// on the process. Note that this may alter the validation result (validRes) either from successful
// to unsuccessful, or in rare cases, from unsuccessful to successful.
err = AuthDelegate->HandleCertValidationResult(IsInitiator(), validRes, peerCert, msg.PeerNodeId, certSet, certValidContext);
SuccessOrExit(err);
// Check that validation succeeded.
VerifyOrExit((validRes != WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE &&
validRes != WEAVE_ERROR_UNSUPPORTED_SIGNATURE_TYPE),
err = WEAVE_ERROR_UNSUPPORTED_CERT_FORMAT);
VerifyOrExit(validRes == WEAVE_NO_ERROR, err = validRes);
VerifyOrExit(peerCert != NULL, err = WEAVE_ERROR_INCORRECT_STATE);
// Decode the CASE signature from the end of the message.
reader.Init(msg.Signature, msg.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 = msg.Signature - msgStart;
// Generate a hash of the 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(peerCert->PubKeyCurveId), msgHash, ConfigHashLength(),
ecdsaSig, peerCert->PublicKey.EC);
SuccessOrExit(err);
exit:
if (callEndCertValidation)
{
WEAVE_ERROR endErr = AuthDelegate->EndCertValidation(certSet, certValidContext);
if (err == WEAVE_NO_ERROR)
err = endErr;
}
certSet.Release();
return err;
}
WEAVE_ERROR WeaveCASEEngine::DecodeCertificateInfo(BeginSessionMessageBase& msg, WeaveCertificateSet& certSet,
WeaveDN& entityCertDN, CertificateKeyId& entityCertSubjectKeyId)
{
WEAVE_ERROR err;
TLVReader reader;
WeaveCertificateData *entityCert = NULL;
// Begin decoding the certificate information structure.
reader.Init(msg.CertInfo, msg.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 its 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());
}
} // namespace CASE
} // namespace Security
} // namespace Profiles
} // namespace Weave
} // namespace nl