Skip to content

Commit

Permalink
[crypto] Add HKDF key handle and use it during PASE (project-chip#31311)
Browse files Browse the repository at this point in the history
* [crypto] Add HKDF key handle and use it during PASE

Current SPAKE2+ interface assumes that raw shared secret
is extracted and used by the application to derive session
keys. This prevents using secure crypto APIs, such as PSA,
to perform SPAKE2+ and do the key derivation in a secure
environment, and isolate the application from key material.

1. Add Hkdf128KeyHandle type and add methods for deriving
   session keys from an HKDF key.
2. Change SPAKE2+ interface to return HKDF key handle
   instead of raw key secret.

A similar approach can be taken to improve CASE security
in the future though we would need 256-bit HKDF key support
in such a case.

* Change HKDF key handle to hold key of any length

* Code review
  • Loading branch information
Damian-Nordic authored and erwinpan1 committed Feb 12, 2024
1 parent 2a8b325 commit 2fa20d4
Show file tree
Hide file tree
Showing 16 changed files with 368 additions and 117 deletions.
16 changes: 6 additions & 10 deletions src/crypto/CHIPCryptoPAL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
*/

#include "CHIPCryptoPAL.h"

#include "SessionKeystore.h"

#include <lib/asn1/ASN1.h>
#include <lib/asn1/ASN1Macros.h>
#include <lib/core/CHIPEncoding.h>
Expand Down Expand Up @@ -498,18 +501,11 @@ CHIP_ERROR Spake2p::KeyConfirm(const uint8_t * in, size_t in_len)
return CHIP_NO_ERROR;
}

CHIP_ERROR Spake2p::GetKeys(uint8_t * out, size_t * out_len)
CHIP_ERROR Spake2p::GetKeys(SessionKeystore & keystore, HkdfKeyHandle & key) const
{
CHIP_ERROR error = CHIP_ERROR_INTERNAL;

VerifyOrExit(state == CHIP_SPAKE2P_STATE::KC, error = CHIP_ERROR_INTERNAL);
VerifyOrExit(*out_len >= hash_size / 2, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(state == CHIP_SPAKE2P_STATE::KC, CHIP_ERROR_INTERNAL);

memcpy(out, Ke, hash_size / 2);
error = CHIP_NO_ERROR;
exit:
*out_len = hash_size / 2;
return error;
return keystore.CreateKey(ByteSpan(Ke, hash_size / 2), key);
}

CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::InitImpl()
Expand Down
77 changes: 40 additions & 37 deletions src/crypto/CHIPCryptoPAL.h
Original file line number Diff line number Diff line change
Expand Up @@ -593,27 +593,24 @@ class P256Keypair : public P256KeypairBase
bool mInitialized = false;
};

using Symmetric128BitsKeyByteArray = uint8_t[CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES];

/**
* @brief Platform-specific Symmetric key handle
* @brief Platform-specific symmetric key handle
*
* The class represents a key used by the Matter stack either in the form of raw key material or key
* reference, depending on the platform. To achieve that, it contains an opaque context that can be
* cast to a concrete representation used by the given platform. Note that currently Matter uses
* 128-bit symmetric keys only.
* cast to a concrete representation used by the given platform.
*
* @note Symmetric128BitsKeyHandle is an abstract class to force child classes for each key handle type.
* Symmetric128BitsKeyHandle class implements all the necessary components for handles.
* Child classes only need to implement a constructor and delete all the copy operators.
* @note SymmetricKeyHandle is an abstract class to force child classes for each key handle type.
* SymmetricKeyHandle class implements all the necessary components for handles.
*/
class Symmetric128BitsKeyHandle
template <size_t ContextSize>
class SymmetricKeyHandle
{
public:
Symmetric128BitsKeyHandle(const Symmetric128BitsKeyHandle &) = delete;
Symmetric128BitsKeyHandle(Symmetric128BitsKeyHandle &&) = delete;
void operator=(const Symmetric128BitsKeyHandle &) = delete;
void operator=(Symmetric128BitsKeyHandle &&) = delete;
SymmetricKeyHandle(const SymmetricKeyHandle &) = delete;
SymmetricKeyHandle(SymmetricKeyHandle &&) = delete;
void operator=(const SymmetricKeyHandle &) = delete;
void operator=(SymmetricKeyHandle &&) = delete;

/**
* @brief Get internal context cast to the desired key representation
Expand All @@ -634,44 +631,44 @@ class Symmetric128BitsKeyHandle
}

protected:
Symmetric128BitsKeyHandle() = default;
~Symmetric128BitsKeyHandle() { ClearSecretData(mContext.mOpaque); }
SymmetricKeyHandle() = default;
~SymmetricKeyHandle() { ClearSecretData(mContext.mOpaque); }

private:
static constexpr size_t kContextSize = CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES;

struct alignas(uintptr_t) OpaqueContext
{
uint8_t mOpaque[kContextSize] = {};
uint8_t mOpaque[ContextSize] = {};
} mContext;
};

using Symmetric128BitsKeyByteArray = uint8_t[CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES];

/**
* @brief Platform-specific AES key handle
* @brief Platform-specific 128-bit symmetric key handle
*/
class Aes128KeyHandle final : public Symmetric128BitsKeyHandle
class Symmetric128BitsKeyHandle : public SymmetricKeyHandle<CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES>
{
public:
Aes128KeyHandle() = default;
};

Aes128KeyHandle(const Aes128KeyHandle &) = delete;
Aes128KeyHandle(Aes128KeyHandle &&) = delete;
void operator=(const Aes128KeyHandle &) = delete;
void operator=(Aes128KeyHandle &&) = delete;
/**
* @brief Platform-specific 128-bit AES key handle
*/
class Aes128KeyHandle final : public Symmetric128BitsKeyHandle
{
};

/**
* @brief Platform-specific HMAC key handle
* @brief Platform-specific 128-bit HMAC key handle
*/
class Hmac128KeyHandle final : public Symmetric128BitsKeyHandle
{
public:
Hmac128KeyHandle() = default;
};

Hmac128KeyHandle(const Hmac128KeyHandle &) = delete;
Hmac128KeyHandle(Hmac128KeyHandle &&) = delete;
void operator=(const Hmac128KeyHandle &) = delete;
void operator=(Hmac128KeyHandle &&) = delete;
/**
* @brief Platform-specific HKDF key handle
*/
class HkdfKeyHandle final : public SymmetricKeyHandle<CHIP_CONFIG_HKDF_KEY_HANDLE_CONTEXT_SIZE>
{
};

/**
Expand Down Expand Up @@ -1090,6 +1087,9 @@ class PBKDF2_sha256
unsigned int iteration_count, uint32_t key_length, uint8_t * output);
};

// TODO: Extract Spake2p to a separate header and replace the forward declaration with #include SessionKeystore.h
class SessionKeystore;

/**
* The below class implements the draft 01 version of the Spake2+ protocol as
* defined in https://www.ietf.org/id/draft-bar-cfrg-spake2plus-01.html.
Expand Down Expand Up @@ -1205,14 +1205,17 @@ class Spake2p
virtual CHIP_ERROR KeyConfirm(const uint8_t * in, size_t in_len);

/**
* @brief Return the shared secret.
* @brief Return the shared HKDF key.
*
* Returns the shared key established during the Spake2+ process, which can be used
* to derive application-specific keys using HKDF.
*
* @param out The output secret.
* @param out_len The output secret length.
* @param keystore The session keystore for managing the HKDF key lifetime.
* @param key The output HKDF key.
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
CHIP_ERROR GetKeys(uint8_t * out, size_t * out_len);
CHIP_ERROR GetKeys(SessionKeystore & keystore, HkdfKeyHandle & key) const;

CHIP_ERROR InternalHash(const uint8_t * in, size_t in_len);
CHIP_ERROR WriteMN();
Expand Down
19 changes: 14 additions & 5 deletions src/crypto/CHIPCryptoPALPSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ void Hash_SHA256_stream::Clear()
psa_hash_abort(toHashOperation(&mContext));
}

CHIP_ERROR PsaKdf::Init(psa_algorithm_t algorithm, const ByteSpan & secret, const ByteSpan & salt, const ByteSpan & info)
CHIP_ERROR PsaKdf::Init(const ByteSpan & secret, const ByteSpan & salt, const ByteSpan & info)
{
psa_status_t status = PSA_SUCCESS;
psa_key_attributes_t attrs = PSA_KEY_ATTRIBUTES_INIT;
Expand All @@ -284,7 +284,17 @@ CHIP_ERROR PsaKdf::Init(psa_algorithm_t algorithm, const ByteSpan & secret, cons
psa_reset_key_attributes(&attrs);
VerifyOrReturnError(status == PSA_SUCCESS, CHIP_ERROR_INTERNAL);

status = psa_key_derivation_setup(&mOperation, algorithm);
return InitOperation(mSecretKeyId, salt, info);
}

CHIP_ERROR PsaKdf::Init(const HkdfKeyHandle & hkdfKey, const ByteSpan & salt, const ByteSpan & info)
{
return InitOperation(hkdfKey.As<psa_key_id_t>(), salt, info);
}

CHIP_ERROR PsaKdf::InitOperation(psa_key_id_t hkdfKey, const ByteSpan & salt, const ByteSpan & info)
{
psa_status_t status = psa_key_derivation_setup(&mOperation, PSA_ALG_HKDF(PSA_ALG_SHA_256));
VerifyOrReturnError(status == PSA_SUCCESS, CHIP_ERROR_INTERNAL);

if (salt.size() > 0)
Expand All @@ -293,7 +303,7 @@ CHIP_ERROR PsaKdf::Init(psa_algorithm_t algorithm, const ByteSpan & secret, cons
VerifyOrReturnError(status == PSA_SUCCESS, CHIP_ERROR_INTERNAL);
}

status = psa_key_derivation_input_key(&mOperation, PSA_KEY_DERIVATION_INPUT_SECRET, mSecretKeyId);
status = psa_key_derivation_input_key(&mOperation, PSA_KEY_DERIVATION_INPUT_SECRET, hkdfKey);
VerifyOrReturnError(status == PSA_SUCCESS, CHIP_ERROR_INTERNAL);

status = psa_key_derivation_input_bytes(&mOperation, PSA_KEY_DERIVATION_INPUT_INFO, info.data(), info.size());
Expand Down Expand Up @@ -328,8 +338,7 @@ CHIP_ERROR HKDF_sha::HKDF_SHA256(const uint8_t * secret, const size_t secret_len

PsaKdf kdf;

ReturnErrorOnFailure(kdf.Init(PSA_ALG_HKDF(PSA_ALG_SHA_256), ByteSpan(secret, secret_length), ByteSpan(salt, salt_length),
ByteSpan(info, info_length)));
ReturnErrorOnFailure(kdf.Init(ByteSpan(secret, secret_length), ByteSpan(salt, salt_length), ByteSpan(info, info_length)));

return kdf.DeriveBytes(MutableByteSpan(out_buffer, out_length));
}
Expand Down
9 changes: 8 additions & 1 deletion src/crypto/CHIPCryptoPALPSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ class PsaKdf
/**
* @brief Initializes the key derivation operation.
*/
CHIP_ERROR Init(psa_algorithm_t algorithm, const ByteSpan & secret, const ByteSpan & salt, const ByteSpan & info);
CHIP_ERROR Init(const ByteSpan & secret, const ByteSpan & salt, const ByteSpan & info);

/**
* @brief Initializes the key derivation operation.
*/
CHIP_ERROR Init(const HkdfKeyHandle & hkdfKey, const ByteSpan & salt, const ByteSpan & info);

/**
* @brief Derives raw key material from the operation.
Expand Down Expand Up @@ -139,6 +144,8 @@ class PsaKdf
CHIP_ERROR DeriveKey(const psa_key_attributes_t & attributes, psa_key_id_t & keyId);

private:
CHIP_ERROR InitOperation(psa_key_id_t hkdfKey, const ByteSpan & salt, const ByteSpan & info);

psa_key_id_t mSecretKeyId = 0;
psa_key_derivation_operation_t mOperation = PSA_KEY_DERIVATION_OPERATION_INIT;
};
Expand Down
61 changes: 57 additions & 4 deletions src/crypto/PSASessionKeystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

#include "PSASessionKeystore.h"

#include <crypto/CHIPCryptoPALPSA.h>

#include <psa/crypto.h>

namespace chip {
Expand Down Expand Up @@ -66,6 +64,24 @@ class HmacKeyAttributes
psa_key_attributes_t mAttrs = PSA_KEY_ATTRIBUTES_INIT;
};

class HkdfKeyAttributes
{
public:
HkdfKeyAttributes()
{
psa_set_key_type(&mAttrs, PSA_KEY_TYPE_DERIVE);
psa_set_key_algorithm(&mAttrs, PSA_ALG_HKDF(PSA_ALG_SHA_256));
psa_set_key_usage_flags(&mAttrs, PSA_KEY_USAGE_DERIVE);
}

~HkdfKeyAttributes() { psa_reset_key_attributes(&mAttrs); }

const psa_key_attributes_t & Get() { return mAttrs; }

private:
psa_key_attributes_t mAttrs = PSA_KEY_ATTRIBUTES_INIT;
};

} // namespace

CHIP_ERROR PSASessionKeystore::CreateKey(const Symmetric128BitsKeyByteArray & keyMaterial, Aes128KeyHandle & key)
Expand Down Expand Up @@ -95,11 +111,24 @@ CHIP_ERROR PSASessionKeystore::CreateKey(const Symmetric128BitsKeyByteArray & ke
return CHIP_NO_ERROR;
}

CHIP_ERROR PSASessionKeystore::CreateKey(const ByteSpan & keyMaterial, HkdfKeyHandle & key)
{
// Destroy the old key if already allocated
psa_destroy_key(key.As<psa_key_id_t>());

HkdfKeyAttributes attrs;
psa_status_t status = psa_import_key(&attrs.Get(), keyMaterial.data(), keyMaterial.size(), &key.AsMutable<psa_key_id_t>());

VerifyOrReturnError(status == PSA_SUCCESS, CHIP_ERROR_INTERNAL);

return CHIP_NO_ERROR;
}

CHIP_ERROR PSASessionKeystore::DeriveKey(const P256ECDHDerivedSecret & secret, const ByteSpan & salt, const ByteSpan & info,
Aes128KeyHandle & key)
{
PsaKdf kdf;
ReturnErrorOnFailure(kdf.Init(PSA_ALG_HKDF(PSA_ALG_SHA_256), secret.Span(), salt, info));
ReturnErrorOnFailure(kdf.Init(secret.Span(), salt, info));

AesKeyAttributes attrs;

Expand All @@ -111,8 +140,24 @@ CHIP_ERROR PSASessionKeystore::DeriveSessionKeys(const ByteSpan & secret, const
AttestationChallenge & attestationChallenge)
{
PsaKdf kdf;
ReturnErrorOnFailure(kdf.Init(PSA_ALG_HKDF(PSA_ALG_SHA_256), secret, salt, info));
ReturnErrorOnFailure(kdf.Init(secret, salt, info));

return DeriveSessionKeys(kdf, i2rKey, r2iKey, attestationChallenge);
}

CHIP_ERROR PSASessionKeystore::DeriveSessionKeys(const HkdfKeyHandle & hkdfKey, const ByteSpan & salt, const ByteSpan & info,
Aes128KeyHandle & i2rKey, Aes128KeyHandle & r2iKey,
AttestationChallenge & attestationChallenge)
{
PsaKdf kdf;
ReturnErrorOnFailure(kdf.Init(hkdfKey, salt, info));

return DeriveSessionKeys(kdf, i2rKey, r2iKey, attestationChallenge);
}

CHIP_ERROR PSASessionKeystore::DeriveSessionKeys(PsaKdf & kdf, Aes128KeyHandle & i2rKey, Aes128KeyHandle & r2iKey,
AttestationChallenge & attestationChallenge)
{
CHIP_ERROR error;
AesKeyAttributes attrs;

Expand All @@ -138,5 +183,13 @@ void PSASessionKeystore::DestroyKey(Symmetric128BitsKeyHandle & key)
keyId = 0;
}

void PSASessionKeystore::DestroyKey(HkdfKeyHandle & key)
{
auto & keyId = key.AsMutable<psa_key_id_t>();

psa_destroy_key(keyId);
keyId = 0;
}

} // namespace Crypto
} // namespace chip
10 changes: 10 additions & 0 deletions src/crypto/PSASessionKeystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#pragma once

#include <crypto/CHIPCryptoPALPSA.h>
#include <crypto/SessionKeystore.h>

namespace chip {
Expand All @@ -27,11 +28,20 @@ class PSASessionKeystore : public SessionKeystore
public:
CHIP_ERROR CreateKey(const Symmetric128BitsKeyByteArray & keyMaterial, Aes128KeyHandle & key) override;
CHIP_ERROR CreateKey(const Symmetric128BitsKeyByteArray & keyMaterial, Hmac128KeyHandle & key) override;
CHIP_ERROR CreateKey(const ByteSpan & keyMaterial, HkdfKeyHandle & key) override;
CHIP_ERROR DeriveKey(const P256ECDHDerivedSecret & secret, const ByteSpan & salt, const ByteSpan & info,
Aes128KeyHandle & key) override;
CHIP_ERROR DeriveSessionKeys(const ByteSpan & secret, const ByteSpan & salt, const ByteSpan & info, Aes128KeyHandle & i2rKey,
Aes128KeyHandle & r2iKey, AttestationChallenge & attestationChallenge) override;
CHIP_ERROR DeriveSessionKeys(const HkdfKeyHandle & hkdfKey, const ByteSpan & salt, const ByteSpan & info,
Aes128KeyHandle & i2rKey, Aes128KeyHandle & r2iKey,
AttestationChallenge & attestationChallenge) override;
void DestroyKey(Symmetric128BitsKeyHandle & key) override;
void DestroyKey(HkdfKeyHandle & key) override;

private:
CHIP_ERROR DeriveSessionKeys(PsaKdf & kdf, Aes128KeyHandle & i2rKey, Aes128KeyHandle & r2iKey,
AttestationChallenge & attestationChallenge);
};

} // namespace Crypto
Expand Down
Loading

0 comments on commit 2fa20d4

Please sign in to comment.