Skip to content

Commit

Permalink
Updated PASEVerifier Format to Match Spec. (#15070)
Browse files Browse the repository at this point in the history
In the current SDK implementation the PASEVerifier format is (w0s, w1s).
The spec defines PASEVerifier on the device side should have (w0, L) format.
This is also the format that devices should store PASEVerifier in the persisted
memory.

There are some minor changes to adjust to the new implementation.

Also updated test vectors and spake2p tool now generates PASEVerifier in a correct
format.
  • Loading branch information
emargolis authored and pull[bot] committed Jan 24, 2024
1 parent 92ecc58 commit 2647093
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 83 deletions.
10 changes: 9 additions & 1 deletion src/crypto/CHIPCryptoPAL.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -507,6 +507,14 @@ CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::KDF(const uint8_t * ikm, const size_t
return CHIP_NO_ERROR;
}

CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::ComputeW0(uint8_t * w0out, size_t * w0_len, const uint8_t * w0sin, size_t w0sin_len)
{
ReturnErrorOnFailure(FELoad(w0sin, w0sin_len, w0));
ReturnErrorOnFailure(FEWrite(w0, w0out, *w0_len));

return CHIP_NO_ERROR;
}

CHIP_ERROR ConvertIntegerRawToDerWithoutTag(const ByteSpan & raw_integer, MutableByteSpan & out_der_integer)
{
return ConvertIntegerRawToDerInternal(raw_integer, out_der_integer, /* include_tag_and_length = */ false);
Expand Down
18 changes: 16 additions & 2 deletions src/crypto/CHIPCryptoPAL.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -849,7 +849,7 @@ class Spake2p
* @param my_identity_len The verifier identity length.
* @param peer_identity The peer identity. May be NULL if identities are not established.
* @param peer_identity_len The peer identity length.
* @param w0in The input w0 (an output from the PBKDF).
* @param w0in The input w0 (a parameter baked into the device or computed with ComputeW0).
* @param w0in_len The input w0 length.
* @param Lin The input L (a parameter baked into the device or computed with ComputeL).
* @param Lin_len The input L length.
Expand Down Expand Up @@ -1045,6 +1045,18 @@ class Spake2p
*/
virtual CHIP_ERROR PointIsValid(void * R) = 0;

/*
* @synopsis Compute w0sin mod p
*
* @param w0out Output field element (modulo p)
* @param w0_len Output field element length
* @param w1sin Input field element
* @param w1sin_len Input field element length
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
virtual CHIP_ERROR ComputeW0(uint8_t * w0out, size_t * w0_len, const uint8_t * w0sin, size_t w0sin_len) = 0;

/*
* @synopsis Compute w1in*G
*
Expand Down Expand Up @@ -1208,6 +1220,8 @@ class Spake2p_P256_SHA256_HKDF_HMAC : public Spake2p
CHIP_ERROR PointInvert(void * R) override;
CHIP_ERROR PointCofactorMul(void * R) override;
CHIP_ERROR PointIsValid(void * R) override;

CHIP_ERROR ComputeW0(uint8_t * w0out, size_t * w0_len, const uint8_t * w0sin, size_t w0sin_len) override;
CHIP_ERROR ComputeL(uint8_t * Lout, size_t * L_len, const uint8_t * w1in, size_t w1in_len) override;

protected:
Expand Down
3 changes: 2 additions & 1 deletion src/include/platform/CHIPDeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,8 @@
*/
#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SPAKE2P_VERIFIER
#define CHIP_DEVICE_CONFIG_USE_TEST_SPAKE2P_VERIFIER \
"q6YMMEFrj0F39eFq1RTP2Vd1E/Av1gUGsQSdDyxzEAEOXkC/2GtO9oGoi3Hp4qhTmFp975Fuow4BuHIvv30OOIVsEs1kwiW7JO8hQX4OROU="
"uWFwqugDNGiEck/po7KHwwMwwqZgN10XuyBajPGuyzUEV/iree4lOrao5GuwnlQ65CJzbeUB49s31EH+NEkg0JVI5MGCQGMMT/SRPFNRODm3wH/MBiehuFc6FJ/" \
"NH6Rmzw=="
#endif

#else
Expand Down
110 changes: 60 additions & 50 deletions src/protocols/secure_channel/PASESession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ void PASESession::Clear()
{
// This function zeroes out and resets the memory used by the object.
// It's done so that no security related information will be leaked.
memset(&mPoint[0], 0, sizeof(mPoint));
memset(&mPASEVerifier, 0, sizeof(mPASEVerifier));
memset(&mKe[0], 0, sizeof(mKe));
mNextExpectedMsg = MsgType::PASE_PakeError;
Expand All @@ -98,7 +97,6 @@ void PASESession::Clear()
}
mKeLen = sizeof(mKe);
mPairingComplete = false;
mComputeVerifier = true;
PairingSession::Clear();
CloseExchange();
}
Expand Down Expand Up @@ -205,54 +203,68 @@ CHIP_ERROR PASESession::Init(uint16_t mySessionId, uint32_t setupCode, SessionEs

ChipLogDetail(SecureChannel, "Assigned local session key ID %d", mySessionId);
SetLocalSessionId(mySessionId);
mSetupPINCode = setupCode;
mComputeVerifier = true;
mSetupPINCode = setupCode;

return CHIP_NO_ERROR;
}

CHIP_ERROR PASESession::ComputePASEVerifier(uint32_t setUpPINCode, uint32_t pbkdf2IterCount, const ByteSpan & salt,
PASEVerifier & verifier)
CHIP_ERROR PASESession::ComputeWS(uint32_t setUpPINCode, uint32_t pbkdf2IterCount, const ByteSpan & salt, uint32_t wsSize,
uint8_t * ws)
{
TRACE_EVENT_SCOPE("ComputePASEVerifier", "PASESession");
ReturnErrorCodeIf(salt.empty(), CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(salt.data() == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(setUpPINCode >= (1 << kSetupPINCodeFieldLengthInBits), CHIP_ERROR_INVALID_ARGUMENT);

PBKDF2_sha256_crypto mPBKDF;
uint8_t littleEndianSetupPINCode[sizeof(uint32_t)];
Encoding::LittleEndian::Put32(littleEndianSetupPINCode, setUpPINCode);

PASEVerifierSerialized serializedVerifier;
ReturnErrorOnFailure(mPBKDF.pbkdf2_sha256(littleEndianSetupPINCode, sizeof(littleEndianSetupPINCode), salt.data(), salt.size(),
pbkdf2IterCount, sizeof(PASEVerifierSerialized), serializedVerifier));
return verifier.Deserialize(ByteSpan(serializedVerifier));
ReturnErrorCodeIf(salt.empty(), CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(setUpPINCode >= (1 << kSetupPINCodeFieldLengthInBits), CHIP_ERROR_INVALID_ARGUMENT);

return mPBKDF.pbkdf2_sha256(littleEndianSetupPINCode, sizeof(littleEndianSetupPINCode), salt.data(), salt.size(),
pbkdf2IterCount, wsSize, ws);
}

CHIP_ERROR PASESession::GeneratePASEVerifier(PASEVerifier & verifier, uint32_t pbkdf2IterCount, const ByteSpan & salt,
bool useRandomPIN, uint32_t & setupPIN)
bool useRandomPIN, uint32_t & setupPINCode)
{
TRACE_EVENT_SCOPE("GeneratePASEVerifier", "PASESession");

if (useRandomPIN)
{
ReturnErrorOnFailure(DRBG_get_bytes(reinterpret_cast<uint8_t *>(&setupPIN), sizeof(setupPIN)));
ReturnErrorOnFailure(DRBG_get_bytes(reinterpret_cast<uint8_t *>(&setupPINCode), sizeof(setupPINCode)));

// Passcodes shall be restricted to the values 00000001 to 99999998 in decimal, see 5.1.1.6
setupPIN = (setupPIN % kSetupPINCodeMaximumValue) + 1;
setupPINCode = (setupPINCode % kSetupPINCodeMaximumValue) + 1;
}

return PASESession::ComputePASEVerifier(setupPIN, pbkdf2IterCount, salt, verifier);
uint8_t serializedWS[kSpake2p_WS_Length * 2] = { 0 };
ReturnErrorOnFailure(ComputeWS(setupPINCode, pbkdf2IterCount, salt, sizeof(serializedWS), serializedWS));

CHIP_ERROR err = CHIP_NO_ERROR;
size_t len;

// Create local spake2p object for W0 and L camputations.
Spake2p_P256_SHA256_HKDF_HMAC spake2p;
uint8_t context[kSHA256_Hash_Length] = { 0 };
SuccessOrExit(err = spake2p.Init(context, sizeof(context)));

// Compute W0
len = sizeof(verifier.mW0);
SuccessOrExit(err = spake2p.ComputeW0(verifier.mW0, &len, &serializedWS[0], kSpake2p_WS_Length));
VerifyOrExit(len == sizeof(verifier.mW0), err = CHIP_ERROR_INTERNAL);

// Compute L
len = sizeof(verifier.mL);
SuccessOrExit(err = spake2p.ComputeL(verifier.mL, &len, &serializedWS[kSpake2p_WS_Length], kSpake2p_WS_Length));
VerifyOrExit(len == sizeof(verifier.mL), err = CHIP_ERROR_INTERNAL);

exit:
spake2p.Clear();
return err;
}

CHIP_ERROR PASESession::SetupSpake2p(uint32_t pbkdf2IterCount, const ByteSpan & salt)
CHIP_ERROR PASESession::SetupSpake2p()
{
TRACE_EVENT_SCOPE("SetupSpake2p", "PASESession");
uint8_t context[kSHA256_Hash_Length] = { 0 };

if (mComputeVerifier)
{
ReturnErrorOnFailure(PASESession::ComputePASEVerifier(mSetupPINCode, pbkdf2IterCount, salt, mPASEVerifier));
}

MutableByteSpan contextSpan{ context };

ReturnErrorOnFailure(mCommissioningHash.Finish(contextSpan));
Expand Down Expand Up @@ -295,7 +307,6 @@ CHIP_ERROR PASESession::WaitForPairing(const PASEVerifier & verifier, uint32_t p
mPairingComplete = false;
mPasscodeID = kDefaultCommissioningPasscodeId;
mLocalMRPConfig = mrpConfig;
mComputeVerifier = false;

SetPeerNodeId(NodeIdFromPAKEKeyId(mPasscodeID));

Expand Down Expand Up @@ -515,10 +526,7 @@ CHIP_ERROR PASESession::SendPBKDFParamResponse(ByteSpan initiatorRandom, bool in

// Update commissioning hash with the pbkdf2 param response that's being sent.
ReturnErrorOnFailure(mCommissioningHash.AddData(ByteSpan{ resp->Start(), resp->DataLength() }));
ReturnErrorOnFailure(SetupSpake2p(mIterationCount, ByteSpan(mSalt, mSaltLength)));

size_t sizeof_point = sizeof(mPoint);
ReturnErrorOnFailure(mSpake2p.ComputeL(mPoint, &sizeof_point, mPASEVerifier.mL, kSpake2p_WS_Length));
ReturnErrorOnFailure(SetupSpake2p());

ReturnErrorOnFailure(
mExchangeCtxt->SendMessage(MsgType::PBKDFParamResponse, std::move(resp), SendFlags(SendMessageFlags::kExpectResponse)));
Expand All @@ -541,9 +549,8 @@ CHIP_ERROR PASESession::HandlePBKDFParamResponse(System::PacketBufferHandle && m
uint8_t random[kPBKDFParamRandomNumberSize];

uint32_t decodeTagIdSeq = 0;
uint32_t iterCount = 0;
uint32_t saltLength = 0;
const uint8_t * salt;
ByteSpan salt;
uint8_t serializedWS[kSpake2p_WS_Length * 2] = { 0 };

ChipLogDetail(SecureChannel, "Received PBKDF param response");

Expand Down Expand Up @@ -579,8 +586,7 @@ CHIP_ERROR PASESession::HandlePBKDFParamResponse(System::PacketBufferHandle && m
}

// TODO - Add a unit test that exercises mHavePBKDFParameters path
err = SetupSpake2p(mIterationCount, ByteSpan(mSalt, mSaltLength));
SuccessOrExit(err);
salt = ByteSpan(mSalt, mSaltLength);
}
else
{
Expand All @@ -590,24 +596,30 @@ CHIP_ERROR PASESession::HandlePBKDFParamResponse(System::PacketBufferHandle && m

SuccessOrExit(err = tlvReader.Next());
VerifyOrExit(TLV::TagNumFromTag(tlvReader.GetTag()) == ++decodeTagIdSeq, err = CHIP_ERROR_INVALID_TLV_TAG);
SuccessOrExit(err = tlvReader.Get(iterCount));
SuccessOrExit(err = tlvReader.Get(mIterationCount));

SuccessOrExit(err = tlvReader.Next());
VerifyOrExit(TLV::TagNumFromTag(tlvReader.GetTag()) == ++decodeTagIdSeq, err = CHIP_ERROR_INVALID_TLV_TAG);
saltLength = tlvReader.GetLength();
SuccessOrExit(err = tlvReader.GetDataPtr(salt));
SuccessOrExit(err = tlvReader.Get(salt));

SuccessOrExit(err = tlvReader.ExitContainer(containerType));

if (tlvReader.Next() != CHIP_END_OF_TLV)
{
SuccessOrExit(err = DecodeMRPParametersIfPresent(TLV::ContextTag(5), tlvReader));
}

err = SetupSpake2p(iterCount, ByteSpan(salt, saltLength));
SuccessOrExit(err);
}

err = SetupSpake2p();
SuccessOrExit(err);

err = ComputeWS(mSetupPINCode, mIterationCount, salt, sizeof(serializedWS), serializedWS);
SuccessOrExit(err);

err = mSpake2p.BeginProver(nullptr, 0, nullptr, 0, &serializedWS[0], kSpake2p_WS_Length, &serializedWS[kSpake2p_WS_Length],
kSpake2p_WS_Length);
SuccessOrExit(err);

err = SendMsg1();
SuccessOrExit(err);

Expand Down Expand Up @@ -637,8 +649,6 @@ CHIP_ERROR PASESession::SendMsg1()

constexpr uint8_t kPake1_pA = 1;

ReturnErrorOnFailure(
mSpake2p.BeginProver(nullptr, 0, nullptr, 0, mPASEVerifier.mW0, kSpake2p_WS_Length, mPASEVerifier.mL, kSpake2p_WS_Length));
ReturnErrorOnFailure(mSpake2p.ComputeRoundOne(NULL, 0, X, &X_len));
VerifyOrReturnError(X_len == sizeof(X), CHIP_ERROR_INTERNAL);
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(kPake1_pA), ByteSpan(X)));
Expand Down Expand Up @@ -681,8 +691,8 @@ CHIP_ERROR PASESession::HandleMsg1_and_SendMsg2(System::PacketBufferHandle && ms
VerifyOrExit(TLV::TagNumFromTag(tlvReader.GetTag()) == 1, err = CHIP_ERROR_INVALID_TLV_TAG);
X_len = tlvReader.GetLength();
SuccessOrExit(err = tlvReader.GetDataPtr(X));
SuccessOrExit(
err = mSpake2p.BeginVerifier(nullptr, 0, nullptr, 0, mPASEVerifier.mW0, kSpake2p_WS_Length, mPoint, sizeof(mPoint)));
SuccessOrExit(err = mSpake2p.BeginVerifier(nullptr, 0, nullptr, 0, mPASEVerifier.mW0, kP256_FE_Length, mPASEVerifier.mL,
kP256_Point_Length));

SuccessOrExit(err = mSpake2p.ComputeRoundOne(X, X_len, Y, &Y_len));
VerifyOrReturnError(Y_len == sizeof(Y), CHIP_ERROR_INTERNAL);
Expand Down Expand Up @@ -967,8 +977,8 @@ CHIP_ERROR PASEVerifier::Serialize(MutableByteSpan & outSerialized)
{
VerifyOrReturnError(outSerialized.size() >= kSpake2pSerializedVerifierSize, CHIP_ERROR_INVALID_ARGUMENT);

memcpy(&outSerialized.data()[0], mW0, kSpake2p_WS_Length);
memcpy(&outSerialized.data()[kSpake2p_WS_Length], mL, kSpake2p_WS_Length);
memcpy(&outSerialized.data()[0], mW0, sizeof(mW0));
memcpy(&outSerialized.data()[sizeof(mW0)], mL, sizeof(mL));

outSerialized.reduce_size(kSpake2pSerializedVerifierSize);

Expand All @@ -979,8 +989,8 @@ CHIP_ERROR PASEVerifier::Deserialize(ByteSpan inSerialized)
{
VerifyOrReturnError(inSerialized.size() >= kSpake2pSerializedVerifierSize, CHIP_ERROR_INVALID_ARGUMENT);

memcpy(mW0, &inSerialized.data()[0], kSpake2p_WS_Length);
memcpy(mL, &inSerialized.data()[kSpake2p_WS_Length], kSpake2p_WS_Length);
memcpy(mW0, &inSerialized.data()[0], sizeof(mW0));
memcpy(mL, &inSerialized.data()[sizeof(mW0)], sizeof(mL));

return CHIP_NO_ERROR;
}
Expand Down
23 changes: 9 additions & 14 deletions src/protocols/secure_channel/PASESession.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ constexpr uint32_t kSetupPINCodeUndefinedValue = 0;

using namespace Crypto;

constexpr size_t kSpake2p_WS_Length = kP256_FE_Length + 8;
constexpr size_t kSpake2pSerializedVerifierSize = kSpake2p_WS_Length * 2;
constexpr size_t kSpake2p_WS_Length = kP256_FE_Length + 8;

struct PASESessionSerialized;

Expand All @@ -81,16 +80,16 @@ struct PASESessionSerializable
* This is used when the Verifier should be presented in a serialized form.
* For example, when it is generated using PBKDF function, when stored in the
* memory or when sent over the wire.
* The serialized format is concatentation of 'W0' and 'L' verifier components
* each exactly 'kSpake2p_WS_Length' bytes of length:
* { PASEVerifier.mW0[kSpake2p_WS_Length], PASEVerifier.mL[kSpake2p_WS_Length] }
* The serialized format is concatentation of 'W0' and 'L' verifier components:
* { PASEVerifier.mW0[kP256_FE_Length], PASEVerifier.mL[kP256_Point_Length] }
**/
constexpr size_t kSpake2pSerializedVerifierSize = kP256_FE_Length + kP256_Point_Length;
typedef uint8_t PASEVerifierSerialized[kSpake2pSerializedVerifierSize];

struct PASEVerifier
{
uint8_t mW0[kSpake2p_WS_Length];
uint8_t mL[kSpake2p_WS_Length];
uint8_t mW0[kP256_FE_Length];
uint8_t mL[kP256_Point_Length];

CHIP_ERROR Serialize(MutableByteSpan & outSerialized);
CHIP_ERROR Deserialize(ByteSpan inSerialized);
Expand Down Expand Up @@ -241,10 +240,10 @@ class DLL_EXPORT PASESession : public Messaging::ExchangeDelegate, public Pairin
CHIP_ERROR ValidateReceivedMessage(Messaging::ExchangeContext * exchange, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && msg);

static CHIP_ERROR ComputePASEVerifier(uint32_t mySetUpPINCode, uint32_t pbkdf2IterCount, const ByteSpan & salt,
PASEVerifier & verifier);
static CHIP_ERROR ComputeWS(uint32_t mySetUpPINCode, uint32_t pbkdf2IterCount, const ByteSpan & salt, uint32_t wsSize,
uint8_t * ws);

CHIP_ERROR SetupSpake2p(uint32_t pbkdf2IterCount, const ByteSpan & salt);
CHIP_ERROR SetupSpake2p();

CHIP_ERROR SendPBKDFParamRequest();
CHIP_ERROR HandlePBKDFParamRequest(System::PacketBufferHandle && msg);
Expand Down Expand Up @@ -278,17 +277,13 @@ class DLL_EXPORT PASESession : public Messaging::ExchangeDelegate, public Pairin
#else
Spake2p_P256_SHA256_HKDF_HMAC mSpake2p;
#endif
uint8_t mPoint[kMAX_Point_Length];

/* w0s and w1s */
PASEVerifier mPASEVerifier;

PasscodeId mPasscodeID = kDefaultCommissioningPasscodeId;

uint32_t mSetupPINCode;

bool mComputeVerifier = true;

bool mHavePBKDFParameters = false;

uint8_t mPBKDFLocalRandomData[kPBKDFParamRandomNumberSize];
Expand Down
Loading

0 comments on commit 2647093

Please sign in to comment.