diff --git a/src/crypto/CHIPCryptoPAL.cpp b/src/crypto/CHIPCryptoPAL.cpp index 1154d25a6b..3e58db3a48 100644 --- a/src/crypto/CHIPCryptoPAL.cpp +++ b/src/crypto/CHIPCryptoPAL.cpp @@ -21,6 +21,9 @@ */ #include "CHIPCryptoPAL.h" + +#include "SessionKeystore.h" + #include #include #include @@ -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() diff --git a/src/crypto/CHIPCryptoPAL.h b/src/crypto/CHIPCryptoPAL.h index 924b1f768d..6e85539436 100644 --- a/src/crypto/CHIPCryptoPAL.h +++ b/src/crypto/CHIPCryptoPAL.h @@ -562,27 +562,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 +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 @@ -603,44 +600,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 { -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 +{ }; /** @@ -1059,6 +1056,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. @@ -1174,14 +1174,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(); diff --git a/src/crypto/CHIPCryptoPALPSA.cpp b/src/crypto/CHIPCryptoPALPSA.cpp index 8a72e4ba35..5b0de9e7a4 100644 --- a/src/crypto/CHIPCryptoPALPSA.cpp +++ b/src/crypto/CHIPCryptoPALPSA.cpp @@ -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; @@ -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(), 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) @@ -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()); @@ -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)); } diff --git a/src/crypto/CHIPCryptoPALPSA.h b/src/crypto/CHIPCryptoPALPSA.h index 0f71d5e835..23af690b6b 100644 --- a/src/crypto/CHIPCryptoPALPSA.h +++ b/src/crypto/CHIPCryptoPALPSA.h @@ -110,7 +110,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. @@ -140,6 +145,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; }; diff --git a/src/crypto/PSASessionKeystore.cpp b/src/crypto/PSASessionKeystore.cpp index 28f8f002e1..7a0fc4af10 100644 --- a/src/crypto/PSASessionKeystore.cpp +++ b/src/crypto/PSASessionKeystore.cpp @@ -17,8 +17,6 @@ #include "PSASessionKeystore.h" -#include - #include namespace chip { @@ -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) @@ -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()); + + HkdfKeyAttributes attrs; + psa_status_t status = psa_import_key(&attrs.Get(), keyMaterial.data(), keyMaterial.size(), &key.AsMutable()); + + 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; @@ -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; @@ -138,5 +183,13 @@ void PSASessionKeystore::DestroyKey(Symmetric128BitsKeyHandle & key) keyId = 0; } +void PSASessionKeystore::DestroyKey(HkdfKeyHandle & key) +{ + auto & keyId = key.AsMutable(); + + psa_destroy_key(keyId); + keyId = 0; +} + } // namespace Crypto } // namespace chip diff --git a/src/crypto/PSASessionKeystore.h b/src/crypto/PSASessionKeystore.h index 690194fcd4..1a55713b82 100644 --- a/src/crypto/PSASessionKeystore.h +++ b/src/crypto/PSASessionKeystore.h @@ -17,6 +17,7 @@ #pragma once +#include #include namespace chip { @@ -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 diff --git a/src/crypto/RawKeySessionKeystore.cpp b/src/crypto/RawKeySessionKeystore.cpp index 949d30a857..5192962da0 100644 --- a/src/crypto/RawKeySessionKeystore.cpp +++ b/src/crypto/RawKeySessionKeystore.cpp @@ -19,10 +19,22 @@ #include +#include + namespace chip { namespace Crypto { -using HKDF_sha_crypto = HKDF_sha; +// The underlying representation of the HKDF key handle +struct RawHkdfKeyHandle +{ + ByteSpan Span() const { return ByteSpan(data, size); } + + // Cap the data size so that the entire structure fits in the opaque context of the HKDF key handle. + static constexpr size_t kMaxDataSize = std::min(CHIP_CONFIG_HKDF_KEY_HANDLE_CONTEXT_SIZE - sizeof(uint8_t), UINT8_MAX); + + uint8_t data[kMaxDataSize]; + uint8_t size; +}; CHIP_ERROR RawKeySessionKeystore::CreateKey(const Symmetric128BitsKeyByteArray & keyMaterial, Aes128KeyHandle & key) { @@ -36,10 +48,21 @@ CHIP_ERROR RawKeySessionKeystore::CreateKey(const Symmetric128BitsKeyByteArray & return CHIP_NO_ERROR; } +CHIP_ERROR RawKeySessionKeystore::CreateKey(const ByteSpan & keyMaterial, HkdfKeyHandle & key) +{ + RawHkdfKeyHandle & rawKey = key.AsMutable(); + + VerifyOrReturnError(keyMaterial.size() <= sizeof(rawKey.data), CHIP_ERROR_BUFFER_TOO_SMALL); + memcpy(rawKey.data, keyMaterial.data(), keyMaterial.size()); + rawKey.size = static_cast(keyMaterial.size()); + + return CHIP_NO_ERROR; +} + CHIP_ERROR RawKeySessionKeystore::DeriveKey(const P256ECDHDerivedSecret & secret, const ByteSpan & salt, const ByteSpan & info, Aes128KeyHandle & key) { - HKDF_sha_crypto hkdf; + HKDF_sha hkdf; return hkdf.HKDF_SHA256(secret.ConstBytes(), secret.Length(), salt.data(), salt.size(), info.data(), info.size(), key.AsMutable(), sizeof(Symmetric128BitsKeyByteArray)); @@ -49,7 +72,7 @@ CHIP_ERROR RawKeySessionKeystore::DeriveSessionKeys(const ByteSpan & secret, con Aes128KeyHandle & i2rKey, Aes128KeyHandle & r2iKey, AttestationChallenge & attestationChallenge) { - HKDF_sha_crypto hkdf; + HKDF_sha hkdf; uint8_t keyMaterial[2 * sizeof(Symmetric128BitsKeyByteArray) + AttestationChallenge::Capacity()]; ReturnErrorOnFailure(hkdf.HKDF_SHA256(secret.data(), secret.size(), salt.data(), salt.size(), info.data(), info.size(), @@ -63,10 +86,25 @@ CHIP_ERROR RawKeySessionKeystore::DeriveSessionKeys(const ByteSpan & secret, con .StatusCode(); } +CHIP_ERROR RawKeySessionKeystore::DeriveSessionKeys(const HkdfKeyHandle & hkdfKey, const ByteSpan & salt, const ByteSpan & info, + Aes128KeyHandle & i2rKey, Aes128KeyHandle & r2iKey, + AttestationChallenge & attestationChallenge) +{ + return DeriveSessionKeys(hkdfKey.As().Span(), salt, info, i2rKey, r2iKey, attestationChallenge); +} + void RawKeySessionKeystore::DestroyKey(Symmetric128BitsKeyHandle & key) { ClearSecretData(key.AsMutable()); } +void RawKeySessionKeystore::DestroyKey(HkdfKeyHandle & key) +{ + RawHkdfKeyHandle & rawKey = key.AsMutable(); + + ClearSecretData(rawKey.data); + rawKey.size = 0; +} + } // namespace Crypto } // namespace chip diff --git a/src/crypto/RawKeySessionKeystore.h b/src/crypto/RawKeySessionKeystore.h index 51f6c92750..2b8d2dd9ca 100644 --- a/src/crypto/RawKeySessionKeystore.h +++ b/src/crypto/RawKeySessionKeystore.h @@ -27,11 +27,16 @@ class RawKeySessionKeystore : 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; }; } // namespace Crypto diff --git a/src/crypto/SessionKeystore.h b/src/crypto/SessionKeystore.h index dbf0ea3996..7f9e1d2858 100644 --- a/src/crypto/SessionKeystore.h +++ b/src/crypto/SessionKeystore.h @@ -60,53 +60,95 @@ class SessionKeystore virtual CHIP_ERROR CreateKey(const Symmetric128BitsKeyByteArray & keyMaterial, Hmac128KeyHandle & key) = 0; /** - * @brief Derive key from a shared secret. + * @brief Import raw key material and return a key handle for an HKDF key. * - * Use HKDF as defined in the Matter specification to derive an AES key from the shared secret. + * @note This method should only be used when using the raw key material in the Matter stack + * cannot be avoided. Ideally, crypto interfaces should allow platforms to perform all the + * cryptographic operations in a secure environment. * * If the method returns no error, the application is responsible for destroying the handle - * using DestroyKey() method when the key is no longer needed. + * using the DestroyKey() method when the key is no longer needed. + */ + virtual CHIP_ERROR CreateKey(const ByteSpan & keyMaterial, HkdfKeyHandle & key) = 0; + + /** + * @brief Destroy key. + * + * The method can take an uninitialized handle in which case it is a no-op. + * As a result of calling this method, the handle is put in the uninitialized state. + */ + virtual void DestroyKey(Symmetric128BitsKeyHandle & key) = 0; + + /** + * @brief Destroy key. + * + * The method can take an uninitialized handle in which case it is a no-op. + * As a result of calling this method, the handle is put in the uninitialized state. + */ + virtual void DestroyKey(HkdfKeyHandle & key) = 0; + + /**************************** + * SessionKeyDerivation APIs + *****************************/ + + /** + * @brief Derive key from a session establishment's `SharedSecret`. + * + * Use `Crypto_KDF` (HKDF) primitive as defined in the Matter specification to derive + * a symmetric (AES) key from the session establishment's `SharedSecret`. + * + * If the method returns no error, the caller is responsible for destroying the symmetric key + * using the DestroyKey() method when the key is no longer needed. */ virtual CHIP_ERROR DeriveKey(const P256ECDHDerivedSecret & secret, const ByteSpan & salt, const ByteSpan & info, Aes128KeyHandle & key) = 0; /** - * @brief Derive session keys from a shared secret. + * @brief Derive session keys from a session establishment's `SharedSecret`. * - * Use HKDF as defined in the Matter specification to derive AES keys for both directions, and - * the attestation challenge from the shared secret. + * Use `Crypto_KDF` (HKDF) primitive as defined in the Matter specification to derive symmetric + * (AES) session keys for both directions, and the attestation challenge from the session + * establishment's `SharedSecret`. * - * If the method returns no error, the application is responsible for destroying the handles - * using DestroyKey() method when the keys are no longer needed. On failure, the method must - * release all handles that it allocated so far. + * If the method returns no error, the caller is responsible for destroying the symmetric keys + * using the DestroyKey() method when the keys are no longer needed. On failure, the method is + * responsible for releasing all keys that it allocated so far. */ virtual CHIP_ERROR DeriveSessionKeys(const ByteSpan & secret, const ByteSpan & salt, const ByteSpan & info, Aes128KeyHandle & i2rKey, Aes128KeyHandle & r2iKey, AttestationChallenge & attestationChallenge) = 0; /** - * @brief Destroy key. + * @brief Derive session keys from a session establishment's `SharedSecret`. * - * The method can take an uninitialized handle in which case it is a no-op. - * As a result of calling this method, the handle is put in the uninitialized state. + * Use Crypto_KDF (HKDF) primitive as defined in the Matter specification to derive symmetric + * (AES) session keys for both directions, and the attestation challenge from the session + * establishment's `SharedSecret`, represented as the key handle. + * + * If the method returns no error, the caller is responsible for destroying the symmetric keys + * using the DestroyKey() method when the keys are no longer needed. On failure, the method is + * responsible for releasing all keys that it allocated so far. */ - virtual void DestroyKey(Symmetric128BitsKeyHandle & key) = 0; + virtual CHIP_ERROR DeriveSessionKeys(const HkdfKeyHandle & secretKey, const ByteSpan & salt, const ByteSpan & info, + Aes128KeyHandle & i2rKey, Aes128KeyHandle & r2iKey, + AttestationChallenge & attestationChallenge) = 0; }; /** * @brief RAII class to hold a temporary key handle that is destroyed on scope exit. */ -class AutoReleaseSessionKey +template +class AutoReleaseSymmetricKey { public: - explicit AutoReleaseSessionKey(SessionKeystore & keystore) : mKeystore(keystore) {} - ~AutoReleaseSessionKey() { mKeystore.DestroyKey(mKeyHandle); } + explicit AutoReleaseSymmetricKey(SessionKeystore & keystore) : mKeystore(keystore) {} + ~AutoReleaseSymmetricKey() { mKeystore.DestroyKey(mKeyHandle); } - Aes128KeyHandle & KeyHandle() { return mKeyHandle; } + KeyHandleType & KeyHandle() { return mKeyHandle; } private: SessionKeystore & mKeystore; - Aes128KeyHandle mKeyHandle; + KeyHandleType mKeyHandle; }; } // namespace Crypto diff --git a/src/crypto/tests/CHIPCryptoPALTest.cpp b/src/crypto/tests/CHIPCryptoPALTest.cpp index 59ce99ad6d..ba84d0a2d2 100644 --- a/src/crypto/tests/CHIPCryptoPALTest.cpp +++ b/src/crypto/tests/CHIPCryptoPALTest.cpp @@ -126,6 +126,34 @@ class HeapChecker #include "DacValidationExplicitVectors.h" +// Verify that two HKDF keys are equal by checking if they generate the same attestation challenge. +// Note that the keys cannot be compared directly because they are given as key handles. +void AssertKeysEqual(nlTestSuite * inSuite, SessionKeystore & keystore, HkdfKeyHandle & left, const HkdfKeyHandle & right) +{ + auto generateChallenge = [&](const HkdfKeyHandle & key, AttestationChallenge & challenge) -> void { + constexpr uint8_t kTestSalt[] = { 'T', 'E', 'S', 'T', 'S', 'A', 'L', 'T' }; + constexpr uint8_t kTestInfo[] = { 'T', 'E', 'S', 'T', 'I', 'N', 'F', 'O' }; + + Aes128KeyHandle i2rKey; + Aes128KeyHandle r2iKey; + + CHIP_ERROR error = keystore.DeriveSessionKeys(key, ByteSpan(kTestSalt), ByteSpan(kTestInfo), i2rKey, r2iKey, challenge); + NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR); + + // Ignore the keys, just return the attestation challenge + keystore.DestroyKey(i2rKey); + keystore.DestroyKey(r2iKey); + }; + + AttestationChallenge leftChallenge; + AttestationChallenge rightChallenge; + + generateChallenge(left, leftChallenge); + generateChallenge(right, rightChallenge); + + NL_TEST_ASSERT(inSuite, memcmp(leftChallenge.ConstBytes(), rightChallenge.ConstBytes(), AttestationChallenge::Capacity()) == 0); +} + } // namespace static uint32_t gs_test_entropy_source_called = 0; @@ -1839,10 +1867,7 @@ static void TestSPAKE2P_RFC(nlTestSuite * inSuite, void * inContext) size_t Pverifier_len = sizeof(Pverifier); uint8_t Vverifier[kMAX_Hash_Length]; size_t Vverifier_len = sizeof(Vverifier); - uint8_t VKe[kMAX_Hash_Length]; - size_t VKe_len = sizeof(VKe); - uint8_t PKe[kMAX_Hash_Length]; - size_t PKe_len = sizeof(PKe); + DefaultSessionKeystore keystore; int numOfTestVectors = ArraySize(rfc_tvs); int numOfTestsRan = 0; @@ -1939,17 +1964,27 @@ static void TestSPAKE2P_RFC(nlTestSuite * inSuite, void * inContext) error = Verifier.KeyConfirm(Pverifier, Pverifier_len); NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR); - PKe_len = sizeof(PKe); - error = Prover.GetKeys(PKe, &PKe_len); + // Import HKDF key from the test vector to the keystore + HkdfKeyHandle vectorKe; + error = keystore.CreateKey(ByteSpan(vector->Ke, vector->Ke_len), vectorKe); NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR); - NL_TEST_ASSERT(inSuite, PKe_len == vector->Ke_len); - NL_TEST_ASSERT(inSuite, memcmp(PKe, vector->Ke, vector->Ke_len) == 0); - VKe_len = sizeof(VKe); - error = Verifier.GetKeys(VKe, &VKe_len); + // Verify that both sides generated the same HKDF key as in the test vector + // Since the HKDF keys may not be availabe in the raw form, do not compare them directly, + // but rather check if the same attestation challenge is derived from + HkdfKeyHandle PKe; + error = Prover.GetKeys(keystore, PKe); NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR); - NL_TEST_ASSERT(inSuite, VKe_len == vector->Ke_len); - NL_TEST_ASSERT(inSuite, memcmp(VKe, vector->Ke, vector->Ke_len) == 0); + AssertKeysEqual(inSuite, keystore, PKe, vectorKe); + + HkdfKeyHandle VKe; + error = Verifier.GetKeys(keystore, VKe); + NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR); + AssertKeysEqual(inSuite, keystore, VKe, vectorKe); + + keystore.DestroyKey(vectorKe); + keystore.DestroyKey(PKe); + keystore.DestroyKey(VKe); numOfTestsRan += 1; } diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 60efb7118b..c24f1dc1d2 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -218,6 +218,19 @@ #define CHIP_CONFIG_SHA256_CONTEXT_ALIGN size_t #endif // CHIP_CONFIG_SHA256_CONTEXT_ALIGN +/** + * @def CHIP_CONFIG_HKDF_KEY_HANDLE_CONTEXT_SIZE + * + * @brief + * Size of the statically allocated context for the HKDF key handle in CryptoPAL. + * + * The default size is selected so that the key handle is able to fit 256-bit raw key + * material along with the size information. + */ +#ifndef CHIP_CONFIG_HKDF_KEY_HANDLE_CONTEXT_SIZE +#define CHIP_CONFIG_HKDF_KEY_HANDLE_CONTEXT_SIZE (32 + 1) +#endif // CHIP_CONFIG_HKDF_KEY_HANDLE_CONTEXT_SIZE + /** * @def CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS * diff --git a/src/platform/nrfconnect/CHIPPlatformConfig.h b/src/platform/nrfconnect/CHIPPlatformConfig.h index c1d8a3e665..3ece933d37 100644 --- a/src/platform/nrfconnect/CHIPPlatformConfig.h +++ b/src/platform/nrfconnect/CHIPPlatformConfig.h @@ -40,6 +40,7 @@ #ifdef CONFIG_CHIP_CRYPTO_PSA #define CHIP_CONFIG_SHA256_CONTEXT_SIZE sizeof(psa_hash_operation_t) +#define CHIP_CONFIG_HKDF_KEY_HANDLE_CONTEXT_SIZE sizeof(psa_key_id_t) #elif defined(CONFIG_CC3XX_BACKEND) // Size of the statically allocated context for SHA256 operations in CryptoPAL // determined empirically. diff --git a/src/protocols/secure_channel/CASESession.h b/src/protocols/secure_channel/CASESession.h index 7453b6b500..b4c0c249ed 100644 --- a/src/protocols/secure_channel/CASESession.h +++ b/src/protocols/secure_channel/CASESession.h @@ -214,6 +214,8 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, private: friend class TestCASESession; + using AutoReleaseSessionKey = Crypto::AutoReleaseSymmetricKey; + /* * Initialize the object given a reference to the SessionManager, certificate validity policy and a delegate which will be * notified of any further progress on this session. @@ -254,7 +256,7 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, CHIP_ERROR SendSigma2Resume(); - CHIP_ERROR DeriveSigmaKey(const ByteSpan & salt, const ByteSpan & info, Crypto::AutoReleaseSessionKey & key) const; + CHIP_ERROR DeriveSigmaKey(const ByteSpan & salt, const ByteSpan & info, AutoReleaseSessionKey & key) const; CHIP_ERROR ConstructSaltSigma2(const ByteSpan & rand, const Crypto::P256PublicKey & pubkey, const ByteSpan & ipk, MutableByteSpan & salt); CHIP_ERROR ConstructTBSData(const ByteSpan & senderNOC, const ByteSpan & senderICAC, const ByteSpan & senderPubKey, @@ -262,7 +264,7 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, CHIP_ERROR ConstructSaltSigma3(const ByteSpan & ipk, MutableByteSpan & salt); CHIP_ERROR ConstructSigmaResumeKey(const ByteSpan & initiatorRandom, const ByteSpan & resumptionID, const ByteSpan & skInfo, - const ByteSpan & nonce, Crypto::AutoReleaseSessionKey & resumeKey); + const ByteSpan & nonce, AutoReleaseSessionKey & resumeKey); CHIP_ERROR GenerateSigmaResumeMIC(const ByteSpan & initiatorRandom, const ByteSpan & resumptionID, const ByteSpan & skInfo, const ByteSpan & nonce, MutableByteSpan & resumeMIC); diff --git a/src/protocols/secure_channel/PASESession.cpp b/src/protocols/secure_channel/PASESession.cpp index 966bc1dbe8..5e50acb53c 100644 --- a/src/protocols/secure_channel/PASESession.cpp +++ b/src/protocols/secure_channel/PASESession.cpp @@ -91,7 +91,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(&mPASEVerifier, 0, sizeof(mPASEVerifier)); - memset(&mKe[0], 0, sizeof(mKe)); mNextExpectedMsg.ClearValue(); mSpake2p.Clear(); @@ -104,7 +103,6 @@ void PASESession::Clear() chip::Platform::MemoryFree(mSalt); mSalt = nullptr; } - mKeLen = sizeof(mKe); mPairingComplete = false; PairingSession::Clear(); } @@ -258,8 +256,15 @@ void PASESession::OnResponseTimeout(ExchangeContext * ec) CHIP_ERROR PASESession::DeriveSecureSession(CryptoContext & session) const { VerifyOrReturnError(mPairingComplete, CHIP_ERROR_INCORRECT_STATE); - return session.InitFromSecret(*mSessionManager->GetSessionKeystore(), ByteSpan(mKe, mKeLen), ByteSpan(), - CryptoContext::SessionInfoType::kSessionEstablishment, mRole); + + SessionKeystore & keystore = *mSessionManager->GetSessionKeystore(); + AutoReleaseSymmetricKey hkdfKey(keystore); + + ReturnErrorOnFailure(mSpake2p.GetKeys(keystore, hkdfKey.KeyHandle())); + ReturnErrorOnFailure(session.InitFromSecret(keystore, hkdfKey.KeyHandle(), ByteSpan{} /* salt */, + CryptoContext::SessionInfoType::kSessionEstablishment, mRole)); + + return CHIP_NO_ERROR; } CHIP_ERROR PASESession::SendPBKDFParamRequest() @@ -674,7 +679,6 @@ CHIP_ERROR PASESession::HandleMsg2_and_SendMsg3(System::PacketBufferHandle && ms SuccessOrExit(err = mSpake2p.ComputeRoundTwo(Y, Y_len, verifier, &verifier_len)); SuccessOrExit(err = mSpake2p.KeyConfirm(peer_verifier, peer_verifier_len)); - SuccessOrExit(err = mSpake2p.GetKeys(mKe, &mKeLen)); msg2 = nullptr; { @@ -737,7 +741,6 @@ CHIP_ERROR PASESession::HandleMsg3(System::PacketBufferHandle && msg) VerifyOrExit(peer_verifier_len == kMAX_Hash_Length, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH); SuccessOrExit(err = mSpake2p.KeyConfirm(peer_verifier, peer_verifier_len)); - SuccessOrExit(err = mSpake2p.GetKeys(mKe, &mKeLen)); // Send confirmation to peer that we succeeded so they can start using the session. SendStatusReport(mExchangeCtxt, kProtocolCodeSuccess); diff --git a/src/transport/CryptoContext.cpp b/src/transport/CryptoContext.cpp index 97420cfd2d..9b94c2a2fe 100644 --- a/src/transport/CryptoContext.cpp +++ b/src/transport/CryptoContext.cpp @@ -69,7 +69,6 @@ CHIP_ERROR CryptoContext::InitFromSecret(SessionKeystore & keystore, const ByteS SessionInfoType infoType, SessionRole role) { VerifyOrReturnError(mKeyAvailable == false, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(secret.size() > 0, CHIP_ERROR_INVALID_ARGUMENT); ByteSpan info = (infoType == SessionInfoType::kSessionResumption) ? ByteSpan(RSEKeysInfo) : ByteSpan(SEKeysInfo); @@ -79,30 +78,34 @@ CHIP_ERROR CryptoContext::InitFromSecret(SessionKeystore & keystore, const ByteS auto & r2iKey = (role == SessionRole::kInitiator) ? mDecryptionKey : mEncryptionKey; #if CHIP_CONFIG_SECURITY_TEST_MODE + ReturnErrorOnFailure(InitTestMode(keystore, i2rKey, r2iKey)); +#else + ReturnErrorOnFailure(keystore.DeriveSessionKeys(secret, salt, info, i2rKey, r2iKey, mAttestationChallenge)); +#endif - // If enabled, override the generated session key with a known key pair - // to allow man-in-the-middle session key recovery for testing purposes. + mKeyAvailable = true; + mSessionRole = role; + mKeystore = &keystore; - constexpr uint8_t kTestSharedSecret[CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH] = CHIP_CONFIG_TEST_SHARED_SECRET_VALUE; - static_assert(sizeof(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE) == CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH, - "CHIP_CONFIG_TEST_SHARED_SECRET_VALUE must be 32 bytes"); - const ByteSpan & testSalt = ByteSpan(); - (void) info; + return CHIP_NO_ERROR; +} -#warning \ - "Warning: CHIP_CONFIG_SECURITY_TEST_MODE=1 bypassing key negotiation... All sessions will use known, fixed test key, and NodeID=0 in NONCE. Node can only communicate with other nodes built with this flag set. Requires build flag 'treat_warnings_as_errors=false'." - ChipLogError( - SecureChannel, - "Warning: CHIP_CONFIG_SECURITY_TEST_MODE=1 bypassing key negotiation... All sessions will use known, fixed test key, " - "and NodeID=0 in NONCE. " - "Node can only communicate with other nodes built with this flag set."); +CHIP_ERROR CryptoContext::InitFromSecret(Crypto::SessionKeystore & keystore, const Crypto::HkdfKeyHandle & hkdfKey, + const ByteSpan & salt, SessionInfoType infoType, SessionRole role) +{ + VerifyOrReturnError(mKeyAvailable == false, CHIP_ERROR_INCORRECT_STATE); - ReturnErrorOnFailure(keystore.DeriveSessionKeys(ByteSpan(kTestSharedSecret), testSalt, ByteSpan(SEKeysInfo), i2rKey, r2iKey, - mAttestationChallenge)); -#else + ByteSpan info = (infoType == SessionInfoType::kSessionResumption) ? ByteSpan(RSEKeysInfo) : ByteSpan(SEKeysInfo); - ReturnErrorOnFailure(keystore.DeriveSessionKeys(secret, salt, info, i2rKey, r2iKey, mAttestationChallenge)); + // If the secure session is created by session initiator, use the I2R key to encrypt + // messages being transmitted. Otherwise, use the R2I key. + auto & i2rKey = (role == SessionRole::kInitiator) ? mEncryptionKey : mDecryptionKey; + auto & r2iKey = (role == SessionRole::kInitiator) ? mDecryptionKey : mEncryptionKey; +#if CHIP_CONFIG_SECURITY_TEST_MODE + ReturnErrorOnFailure(InitTestMode(keystore, i2rKey, r2iKey)); +#else + ReturnErrorOnFailure(keystore.DeriveSessionKeys(hkdfKey, salt, info, i2rKey, r2iKey, mAttestationChallenge)); #endif mKeyAvailable = true; @@ -125,6 +128,28 @@ CHIP_ERROR CryptoContext::InitFromKeyPair(SessionKeystore & keystore, const Cryp return InitFromSecret(keystore, secret.Span(), salt, infoType, role); } +#if CHIP_CONFIG_SECURITY_TEST_MODE +CHIP_ERROR CryptoContext::InitTestMode(Crypto::SessionKeystore & keystore, Crypto::Aes128KeyHandle & i2rKey, + Crypto::Aes128KeyHandle & r2iKey) +{ + // If enabled, override the generated session key with a known key pair + // to allow man-in-the-middle session key recovery for testing purposes. + + constexpr uint8_t kTestSharedSecret[CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH] = CHIP_CONFIG_TEST_SHARED_SECRET_VALUE; + +#warning \ + "Warning: CHIP_CONFIG_SECURITY_TEST_MODE=1 bypassing key negotiation... All sessions will use known, fixed test key, and NodeID=0 in NONCE. Node can only communicate with other nodes built with this flag set. Requires build flag 'treat_warnings_as_errors=false'." + ChipLogError( + SecureChannel, + "Warning: CHIP_CONFIG_SECURITY_TEST_MODE=1 bypassing key negotiation... All sessions will use known, fixed test key, " + "and NodeID=0 in NONCE. " + "Node can only communicate with other nodes built with this flag set."); + + return keystore.DeriveSessionKeys(ByteSpan(kTestSharedSecret), ByteSpan{} /* salt */, ByteSpan(SEKeysInfo), i2rKey, r2iKey, + mAttestationChallenge); +} +#endif // CHIP_CONFIG_SECURITY_TEST_MODE + CHIP_ERROR CryptoContext::BuildNonce(NonceView nonce, uint8_t securityFlags, uint32_t messageCounter, NodeId nodeId) { Encoding::LittleEndian::BufferWriter bbuf(nonce.data(), nonce.size()); diff --git a/src/transport/CryptoContext.h b/src/transport/CryptoContext.h index b08efdabbc..2556c103ef 100644 --- a/src/transport/CryptoContext.h +++ b/src/transport/CryptoContext.h @@ -84,9 +84,7 @@ class DLL_EXPORT CryptoContext SessionRole role); /** - * @brief - * Derive a shared key. The derived key will be used for encrypting/decrypting - * data exchanged on the secure channel. + * @brief Derive session keys and the attestation challenge from the shared secret. * * @param keystore Session keystore for management of symmetric encryption keys * @param secret A reference to the shared secret @@ -98,6 +96,19 @@ class DLL_EXPORT CryptoContext CHIP_ERROR InitFromSecret(Crypto::SessionKeystore & keystore, const ByteSpan & secret, const ByteSpan & salt, SessionInfoType infoType, SessionRole role); + /** + * @brief Derive session keys and the attestation challenge from the HKDF key. + * + * @param keystore Session keystore for management of symmetric encryption keys + * @param hkdfKey HKDF key handle + * @param salt A reference to the initial salt used for deriving the keys + * @param infoType The info buffer to use for deriving session keys + * @param role Role of the new session (initiator or responder) + * @return CHIP_ERROR The result of key derivation + */ + CHIP_ERROR InitFromSecret(Crypto::SessionKeystore & keystore, const Crypto::HkdfKeyHandle & hkdfKey, const ByteSpan & salt, + SessionInfoType infoType, SessionRole role); + /** @brief Build a Nonce buffer using given parameters for encrypt or decrypt. */ static CHIP_ERROR BuildNonce(NonceView nonce, uint8_t securityFlags, uint32_t messageCounter, NodeId nodeId); @@ -157,6 +168,8 @@ class DLL_EXPORT CryptoContext bool IsResponder() const { return mKeyAvailable && mSessionRole == SessionRole::kResponder; } private: + CHIP_ERROR InitTestMode(Crypto::SessionKeystore & keystore, Crypto::Aes128KeyHandle & i2rKey, Crypto::Aes128KeyHandle & r2iKey); + SessionRole mSessionRole; bool mKeyAvailable;