blob: a815901aa2e5ce0d0dc02b7d5955b9c985dd900f [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* 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
* This file implements the CHIP SPAKE2P Session object that provides
* APIs for constructing spake2p messages and establishing encryption
* keys.
*
* The protocol for handling pA, pB, cB and cA is defined in SPAKE2
* Plus specifications.
* (https://www.ietf.org/id/draft-bar-cfrg-spake2plus-01.html)
*
*/
#include <transport/SecurePairingSession.h>
#include <inttypes.h>
#include <string.h>
#include <core/CHIPSafeCasts.h>
#include <protocols/Protocols.h>
#include <support/BufBound.h>
#include <support/CodeUtils.h>
#include <support/SafeInt.h>
namespace chip {
using namespace Crypto;
const char * kSpake2pContext = "CHIP 1.0 Provisioning";
const char * kSpake2pI2RSessionInfo = "Commissioning I2R Key";
const char * kSpake2pR2ISessionInfo = "Commissioning R2I Key";
SecurePairingSession::SecurePairingSession() {}
SecurePairingSession::~SecurePairingSession()
{
memset(&mPoint[0], 0, sizeof(mPoint));
memset(&mWS[0][0], 0, sizeof(mWS));
memset(&mKe[0], 0, sizeof(mKe));
}
CHIP_ERROR SecurePairingSession::Serialize(SecurePairingSessionSerialized & output)
{
CHIP_ERROR error = CHIP_NO_ERROR;
uint16_t serializedLen = 0;
SecurePairingSessionSerializable serializable;
VerifyOrExit(BASE64_ENCODED_LEN(sizeof(serializable)) <= sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT);
error = ToSerializable(serializable);
SuccessOrExit(error);
serializedLen = chip::Base64Encode(Uint8::to_const_uchar(reinterpret_cast<uint8_t *>(&serializable)),
static_cast<uint16_t>(sizeof(serializable)), Uint8::to_char(output.inner));
VerifyOrExit(serializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(serializedLen < sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT);
output.inner[serializedLen] = '\0';
exit:
return error;
}
CHIP_ERROR SecurePairingSession::Deserialize(SecurePairingSessionSerialized & input)
{
CHIP_ERROR error = CHIP_NO_ERROR;
SecurePairingSessionSerializable serializable;
size_t maxlen = BASE64_ENCODED_LEN(sizeof(serializable));
size_t len = strnlen(Uint8::to_char(input.inner), maxlen);
uint16_t deserializedLen = 0;
VerifyOrExit(len < sizeof(SecurePairingSessionSerialized), error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(CanCastTo<uint16_t>(len), error = CHIP_ERROR_INVALID_ARGUMENT);
memset(&serializable, 0, sizeof(serializable));
deserializedLen =
Base64Decode(Uint8::to_const_char(input.inner), static_cast<uint16_t>(len), Uint8::to_uchar((uint8_t *) &serializable));
VerifyOrExit(deserializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(deserializedLen <= sizeof(serializable), error = CHIP_ERROR_INVALID_ARGUMENT);
error = FromSerializable(serializable);
exit:
return error;
}
CHIP_ERROR SecurePairingSession::ToSerializable(SecurePairingSessionSerializable & serializable)
{
CHIP_ERROR error = CHIP_NO_ERROR;
const NodeId localNodeId = mLocalNodeId.ValueOr(kUndefinedNodeId);
const NodeId peerNodeId = mPeerNodeId.ValueOr(kUndefinedNodeId);
VerifyOrExit(CanCastTo<uint16_t>(mKeLen), error = CHIP_ERROR_INTERNAL);
VerifyOrExit(CanCastTo<uint64_t>(localNodeId), error = CHIP_ERROR_INTERNAL);
VerifyOrExit(CanCastTo<uint64_t>(peerNodeId), error = CHIP_ERROR_INTERNAL);
memset(&serializable, 0, sizeof(serializable));
serializable.mKeLen = static_cast<uint16_t>(mKeLen);
serializable.mPairingComplete = (mPairingComplete) ? 1 : 0;
serializable.mLocalNodeId = localNodeId;
serializable.mPeerNodeId = peerNodeId;
serializable.mLocalKeyId = mLocalKeyId;
serializable.mPeerKeyId = mPeerKeyId;
memcpy(serializable.mKe, mKe, mKeLen);
exit:
return error;
}
CHIP_ERROR SecurePairingSession::FromSerializable(const SecurePairingSessionSerializable & serializable)
{
CHIP_ERROR error = CHIP_NO_ERROR;
mPairingComplete = (serializable.mPairingComplete == 1);
mKeLen = static_cast<size_t>(serializable.mKeLen);
VerifyOrExit(mKeLen <= sizeof(mKe), error = CHIP_ERROR_INVALID_ARGUMENT);
memset(mKe, 0, sizeof(mKe));
memcpy(mKe, serializable.mKe, mKeLen);
mLocalNodeId = Optional<NodeId>::Value(serializable.mLocalNodeId);
mPeerNodeId = Optional<NodeId>::Value(serializable.mPeerNodeId);
mLocalKeyId = serializable.mLocalKeyId;
mPeerKeyId = serializable.mPeerKeyId;
exit:
return error;
}
CHIP_ERROR SecurePairingSession::Init(uint32_t setupCode, uint32_t pbkdf2IterCount, const uint8_t * salt, size_t saltLen,
Optional<NodeId> myNodeId, uint16_t myKeyId, SecurePairingSessionDelegate * delegate)
{
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(salt != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(saltLen > 0, err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(delegate != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
err = mSpake2p.Init(Uint8::from_const_char(kSpake2pContext), strlen(kSpake2pContext));
SuccessOrExit(err);
err = pbkdf2_sha256(reinterpret_cast<const uint8_t *>(&setupCode), sizeof(setupCode), salt, saltLen, pbkdf2IterCount,
sizeof(mWS), &mWS[0][0]);
SuccessOrExit(err);
mDelegate = delegate;
mLocalNodeId = myNodeId;
mLocalKeyId = myKeyId;
exit:
return err;
}
CHIP_ERROR SecurePairingSession::WaitForPairing(uint32_t mySetUpPINCode, uint32_t pbkdf2IterCount, const uint8_t * salt,
size_t saltLen, Optional<NodeId> myNodeId, uint16_t myKeyId,
SecurePairingSessionDelegate * delegate)
{
size_t sizeof_point = sizeof(mPoint);
CHIP_ERROR err = Init(mySetUpPINCode, pbkdf2IterCount, salt, saltLen, myNodeId, myKeyId, delegate);
SuccessOrExit(err);
err = mSpake2p.ComputeL(mPoint, &sizeof_point, &mWS[1][0], kSpake2p_WS_Length);
SuccessOrExit(err);
mNextExpectedMsg = Spake2pMsgType::kSpake2pCompute_pA;
mPairingComplete = false;
exit:
return err;
}
CHIP_ERROR SecurePairingSession::AttachHeaderAndSend(uint8_t msgType, System::PacketBuffer * msgIn)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferHandle msgBuf;
PayloadHeader payloadHeader;
payloadHeader
.SetMessageType(msgType) //
.SetProtocolID(Protocols::kProtocol_SecurityChannel);
uint16_t headerSize = payloadHeader.EncodeSizeBytes();
uint16_t actualEncodedHeaderSize = 0;
msgBuf.Adopt(msgIn);
VerifyOrExit(msgBuf->EnsureReservedSize(headerSize), err = CHIP_ERROR_NO_MEMORY);
msgBuf->SetStart(msgBuf->Start() - headerSize);
err = payloadHeader.Encode(msgBuf->Start(), msgBuf->DataLength(), &actualEncodedHeaderSize);
SuccessOrExit(err);
VerifyOrExit(headerSize == actualEncodedHeaderSize, err = CHIP_ERROR_INTERNAL);
err = mDelegate->SendPairingMessage(PacketHeader().SetSourceNodeId(mLocalNodeId).SetEncryptionKeyID(mLocalKeyId),
payloadHeader.GetEncodePacketFlags(), mPeerAddress, msgBuf.Release_ForNow());
SuccessOrExit(err);
exit:
return err;
}
CHIP_ERROR SecurePairingSession::Pair(const Transport::PeerAddress peerAddress, uint32_t peerSetUpPINCode, uint32_t pbkdf2IterCount,
const uint8_t * salt, size_t saltLen, Optional<NodeId> myNodeId, uint16_t myKeyId,
SecurePairingSessionDelegate * delegate)
{
uint8_t X[kMAX_Point_Length];
size_t X_len = sizeof(X);
uint16_t data_len; // Will be the same as X_len in practice.
System::PacketBufferHandle resp;
CHIP_ERROR err = Init(peerSetUpPINCode, pbkdf2IterCount, salt, saltLen, myNodeId, myKeyId, delegate);
SuccessOrExit(err);
mPeerAddress = peerAddress;
err = mSpake2p.BeginProver(reinterpret_cast<const uint8_t *>(""), 0, reinterpret_cast<const uint8_t *>(""), 0, &mWS[0][0],
kSpake2p_WS_Length, &mWS[1][0], kSpake2p_WS_Length);
SuccessOrExit(err);
err = mSpake2p.ComputeRoundOne(X, &X_len);
SuccessOrExit(err);
VerifyOrExit(CanCastTo<uint16_t>(X_len), err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
data_len = static_cast<uint16_t>(X_len);
resp = System::PacketBuffer::NewWithAvailableSize(data_len);
VerifyOrExit(!resp.IsNull(), err = CHIP_SYSTEM_ERROR_NO_MEMORY);
{
BufBound bbuf(resp->Start(), data_len);
bbuf.Put(&X[0], X_len);
VerifyOrExit(bbuf.Fit(), err = CHIP_ERROR_NO_MEMORY);
}
resp->SetDataLength(data_len);
mNextExpectedMsg = Spake2pMsgType::kSpake2pCompute_pB_cB;
// Call delegate to send the Compute_pA to peer
err = AttachHeaderAndSend(Spake2pMsgType::kSpake2pCompute_pA, resp.Release_ForNow());
SuccessOrExit(err);
return err;
exit:
mNextExpectedMsg = Spake2pMsgType::kSpake2pMsgTypeMax;
return err;
}
CHIP_ERROR SecurePairingSession::DeriveSecureSession(const uint8_t * info, size_t info_len, SecureSession & session)
{
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(info != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(info_len > 0, err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(mPairingComplete, err = CHIP_ERROR_INCORRECT_STATE);
err = session.InitFromSecret(mKe, mKeLen, nullptr, 0, info, info_len);
SuccessOrExit(err);
exit:
return err;
}
CHIP_ERROR SecurePairingSession::HandleCompute_pA(const PacketHeader & header, System::PacketBuffer * msg)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t Y[kMAX_Point_Length];
size_t Y_len = sizeof(Y);
uint8_t verifier[kMAX_Hash_Length];
size_t verifier_len = kMAX_Hash_Length;
uint16_t data_len; // To be initialized once we compute it.
const uint8_t * buf = msg->Start();
size_t buf_len = msg->TotalLength();
System::PacketBufferHandle resp;
VerifyOrExit(buf != nullptr, err = CHIP_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(buf_len == kMAX_Point_Length, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
err = mSpake2p.BeginVerifier(reinterpret_cast<const uint8_t *>(""), 0, reinterpret_cast<const uint8_t *>(""), 0, &mWS[0][0],
kSpake2p_WS_Length, mPoint, sizeof(mPoint));
SuccessOrExit(err);
err = mSpake2p.ComputeRoundOne(Y, &Y_len);
SuccessOrExit(err);
err = mSpake2p.ComputeRoundTwo(buf, buf_len, verifier, &verifier_len);
SuccessOrExit(err);
mPeerKeyId = header.GetEncryptionKeyID();
mPeerNodeId = header.GetSourceNodeId();
// Make sure our addition doesn't overflow.
VerifyOrExit(UINTMAX_MAX - verifier_len >= Y_len, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
VerifyOrExit(CanCastTo<uint16_t>(Y_len + verifier_len), err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
data_len = static_cast<uint16_t>(Y_len + verifier_len);
resp = System::PacketBuffer::NewWithAvailableSize(data_len);
VerifyOrExit(!resp.IsNull(), err = CHIP_SYSTEM_ERROR_NO_MEMORY);
{
BufBound bbuf(resp->Start(), data_len);
bbuf.Put(&Y[0], Y_len);
bbuf.Put(verifier, verifier_len);
VerifyOrExit(bbuf.Fit(), err = CHIP_ERROR_NO_MEMORY);
}
resp->SetDataLength(data_len);
mNextExpectedMsg = Spake2pMsgType::kSpake2pCompute_cA;
// Call delegate to send the Compute_pB_cB to peer
err = AttachHeaderAndSend(Spake2pMsgType::kSpake2pCompute_pB_cB, resp.Release_ForNow());
SuccessOrExit(err);
return err;
exit:
mNextExpectedMsg = Spake2pMsgType::kSpake2pMsgTypeMax;
return err;
}
CHIP_ERROR SecurePairingSession::HandleCompute_pB_cB(const PacketHeader & header, System::PacketBuffer * msg)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t verifier[kMAX_Hash_Length];
size_t verifier_len_raw = kMAX_Hash_Length;
uint16_t verifier_len; // To be inited one we check length is small enough
const uint8_t * buf = msg->Start();
size_t buf_len = msg->TotalLength();
System::PacketBufferHandle resp;
VerifyOrExit(buf != nullptr, err = CHIP_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(buf_len == kMAX_Point_Length + kMAX_Hash_Length, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
err = mSpake2p.ComputeRoundTwo(buf, kMAX_Point_Length, verifier, &verifier_len_raw);
SuccessOrExit(err);
VerifyOrExit(CanCastTo<uint16_t>(verifier_len_raw), err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
verifier_len = static_cast<uint16_t>(verifier_len_raw);
mPeerKeyId = header.GetEncryptionKeyID();
mPeerNodeId = header.GetSourceNodeId();
resp = System::PacketBuffer::NewWithAvailableSize(verifier_len);
VerifyOrExit(!resp.IsNull(), err = CHIP_SYSTEM_ERROR_NO_MEMORY);
{
BufBound bbuf(resp->Start(), verifier_len);
bbuf.Put(verifier, verifier_len);
VerifyOrExit(bbuf.Fit(), err = CHIP_ERROR_NO_MEMORY);
}
resp->SetDataLength(verifier_len);
// Call delegate to send the Compute_cA to peer
err = AttachHeaderAndSend(Spake2pMsgType::kSpake2pCompute_cA, resp.Release_ForNow());
SuccessOrExit(err);
{
const uint8_t * hash = &buf[kMAX_Point_Length];
err = mSpake2p.KeyConfirm(hash, kMAX_Hash_Length);
SuccessOrExit(err);
err = mSpake2p.GetKeys(mKe, &mKeLen);
SuccessOrExit(err);
}
mPairingComplete = true;
// Call delegate to indicate pairing completion
mDelegate->OnPairingComplete();
exit:
mNextExpectedMsg = Spake2pMsgType::kSpake2pMsgTypeMax;
return err;
}
CHIP_ERROR SecurePairingSession::HandleCompute_cA(const PacketHeader & header, System::PacketBuffer * msg)
{
CHIP_ERROR err = CHIP_NO_ERROR;
const uint8_t * hash = msg->Start();
VerifyOrExit(hash != nullptr, err = CHIP_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(msg->TotalLength() == kMAX_Hash_Length, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
VerifyOrExit(header.GetSourceNodeId() == mPeerNodeId, err = CHIP_ERROR_WRONG_NODE_ID);
VerifyOrExit(header.GetEncryptionKeyID() == mPeerKeyId, err = CHIP_ERROR_INVALID_KEY_ID);
err = mSpake2p.KeyConfirm(hash, kMAX_Hash_Length);
SuccessOrExit(err);
err = mSpake2p.GetKeys(mKe, &mKeLen);
SuccessOrExit(err);
mPairingComplete = true;
// Call delegate to indicate pairing completion
mDelegate->OnPairingComplete();
exit:
mNextExpectedMsg = Spake2pMsgType::kSpake2pMsgTypeMax;
return err;
}
CHIP_ERROR SecurePairingSession::HandlePeerMessage(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress,
System::PacketBufferHandle msg)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint16_t headerSize = 0;
PayloadHeader payloadHeader;
VerifyOrExit(!msg.IsNull(), err = CHIP_ERROR_INVALID_ARGUMENT);
err = payloadHeader.Decode(packetHeader.GetFlags(), msg->Start(), msg->DataLength(), &headerSize);
SuccessOrExit(err);
msg->ConsumeHead(headerSize);
VerifyOrExit(payloadHeader.GetProtocolID() == Protocols::kProtocol_SecurityChannel, err = CHIP_ERROR_INVALID_MESSAGE_TYPE);
VerifyOrExit(payloadHeader.GetMessageType() == (uint8_t) mNextExpectedMsg, err = CHIP_ERROR_INVALID_MESSAGE_TYPE);
mPeerAddress = peerAddress;
switch (static_cast<Spake2pMsgType>(payloadHeader.GetMessageType()))
{
case Spake2pMsgType::kSpake2pCompute_pA:
err = HandleCompute_pA(packetHeader, msg.Get_ForNow());
break;
case Spake2pMsgType::kSpake2pCompute_pB_cB:
err = HandleCompute_pB_cB(packetHeader, msg.Get_ForNow());
break;
case Spake2pMsgType::kSpake2pCompute_cA:
err = HandleCompute_cA(packetHeader, msg.Get_ForNow());
break;
default:
err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
break;
};
exit:
// Call delegate to indicate pairing failure
if (err != CHIP_NO_ERROR)
{
mDelegate->OnPairingError(err);
}
return err;
}
} // namespace chip