[privacy] Add generated privacy keys to GroupDataProvider. (#21555)
* [privacy] Revise PrivacyEncrypt/Decrypt APIs.
* [privacy] Implement PrivacyEncrypt/Decrypt APIs.
* [privacy] Add privacy keys to KeyContext and OperationalKey store.
* [privacy] Add tests for SetKeySet properly adding privacy keys.
* [privacy] Consolidate credential derivation.
* Restyled by clang-format
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/src/credentials/GroupDataProviderImpl.cpp b/src/credentials/GroupDataProviderImpl.cpp
index 48acaac..0626a8e 100644
--- a/src/credentials/GroupDataProviderImpl.cpp
+++ b/src/credentials/GroupDataProviderImpl.cpp
@@ -697,21 +697,11 @@
}
};
-struct OperationalKey
-{
- // Validity start time in microseconds since 2000-01-01T00:00:00 UTC ("the Epoch")
- uint64_t start_time;
- // Session Id
- uint16_t hash;
- // Operational group key
- uint8_t value[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES];
-};
-
struct KeySetData : PersistentData<kPersistentBufferMax>
{
static constexpr TLV::Tag TagPolicy() { return TLV::ContextTag(1); }
static constexpr TLV::Tag TagNumKeys() { return TLV::ContextTag(2); }
- static constexpr TLV::Tag TagOperationalKeys() { return TLV::ContextTag(3); }
+ static constexpr TLV::Tag TagGroupCredentials() { return TLV::ContextTag(3); }
static constexpr TLV::Tag TagStartTime() { return TLV::ContextTag(4); }
static constexpr TLV::Tag TagKeyHash() { return TLV::ContextTag(5); }
static constexpr TLV::Tag TagKeyValue() { return TLV::ContextTag(6); }
@@ -725,7 +715,7 @@
uint16_t keyset_id = 0;
GroupDataProvider::SecurityPolicy policy = GroupDataProvider::SecurityPolicy::kCacheAndSync;
uint8_t keys_count = 0;
- OperationalKey operational_keys[KeySet::kEpochKeysMax];
+ Crypto::GroupOperationalCredentials operational_keys[KeySet::kEpochKeysMax];
KeySetData() = default;
KeySetData(chip::FabricIndex fabric, chip::KeysetId id) : fabric_index(fabric) { keyset_id = id; }
@@ -749,7 +739,7 @@
next = kInvalidKeysetId;
}
- OperationalKey * GetCurrentKey()
+ Crypto::GroupOperationalCredentials * GetCurrentGroupCredentials()
{
// An epoch key update SHALL order the keys from oldest to newest,
// the current epoch key having the second newest time if time
@@ -778,14 +768,14 @@
// operational_keys
{
TLV::TLVType array, item;
- ReturnErrorOnFailure(writer.StartContainer(TagOperationalKeys(), TLV::kTLVType_Array, array));
+ ReturnErrorOnFailure(writer.StartContainer(TagGroupCredentials(), TLV::kTLVType_Array, array));
for (auto & key : operational_keys)
{
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, item));
ReturnErrorOnFailure(writer.Put(TagStartTime(), static_cast<uint64_t>(key.start_time)));
ReturnErrorOnFailure(writer.Put(TagKeyHash(), key.hash));
ReturnErrorOnFailure(
- writer.Put(TagKeyValue(), ByteSpan(key.value, Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES)));
+ writer.Put(TagKeyValue(), ByteSpan(key.encryption_key, Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES)));
ReturnErrorOnFailure(writer.EndContainer(item));
}
ReturnErrorOnFailure(writer.EndContainer(array));
@@ -810,9 +800,10 @@
// keys_count
ReturnErrorOnFailure(reader.Next(TagNumKeys()));
ReturnErrorOnFailure(reader.Get(keys_count));
+ // TODO(#21614): Enforce maximum number of 3 keys in a keyset
{
// operational_keys
- ReturnErrorOnFailure(reader.Next(TagOperationalKeys()));
+ ReturnErrorOnFailure(reader.Next(TagGroupCredentials()));
VerifyOrReturnError(TLV::kTLVType_Array == reader.GetType(), CHIP_ERROR_INTERNAL);
TLV::TLVType array, item;
@@ -830,11 +821,14 @@
ReturnErrorOnFailure(reader.Next(TagKeyHash()));
ReturnErrorOnFailure(reader.Get(key.hash));
// key value
- ByteSpan value;
+ ByteSpan encryption_key;
ReturnErrorOnFailure(reader.Next(TagKeyValue()));
- ReturnErrorOnFailure(reader.Get(value));
- VerifyOrReturnError(Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES == value.size(), CHIP_ERROR_INTERNAL);
- memcpy(key.value, value.data(), value.size());
+ ReturnErrorOnFailure(reader.Get(encryption_key));
+ VerifyOrReturnError(Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES == encryption_key.size(), CHIP_ERROR_INTERNAL);
+ memcpy(key.encryption_key, encryption_key.data(), encryption_key.size());
+ // Re-derive privacy key from encryption key when loading from storage to save on storage size.
+ MutableByteSpan privacy_key(key.privacy_key);
+ ReturnErrorOnFailure(Crypto::DeriveGroupPrivacyKey(encryption_key, privacy_key));
ReturnErrorOnFailure(reader.ExitContainer(item));
}
ReturnErrorOnFailure(reader.ExitContainer(array));
@@ -899,7 +893,7 @@
mEndpointIterators.ReleaseAll();
mKeySetIterators.ReleaseAll();
mGroupSessionsIterator.ReleaseAll();
- mKeyContexPool.ReleaseAll();
+ mGroupKeyContexPool.ReleaseAll();
}
void GroupDataProviderImpl::SetStorageDelegate(PersistentStorageDelegate * storage)
@@ -1599,9 +1593,8 @@
for (size_t i = 0; i < in_keyset.num_keys_used; ++i)
{
ByteSpan epoch_key(in_keyset.epoch_keys[i].key, Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES);
- MutableByteSpan key_span(keyset.operational_keys[i].value);
- ReturnErrorOnFailure(Crypto::DeriveGroupOperationalKey(epoch_key, compressed_fabric_id, key_span));
- ReturnErrorOnFailure(Crypto::DeriveGroupSessionId(key_span, keyset.operational_keys[i].hash));
+ ReturnErrorOnFailure(
+ Crypto::DeriveGroupOperationalCredentials(epoch_key, compressed_fabric_id, keyset.operational_keys[i]));
}
if (found)
@@ -1638,6 +1631,7 @@
out_keyset.epoch_keys[0].start_time = keyset.operational_keys[0].start_time;
out_keyset.epoch_keys[1].start_time = keyset.operational_keys[1].start_time;
out_keyset.epoch_keys[2].start_time = keyset.operational_keys[2].start_time;
+
return CHIP_NO_ERROR;
}
@@ -1791,11 +1785,12 @@
// Group found, get the keyset
KeySetData keyset;
VerifyOrReturnError(keyset.Find(mStorage, fabric, mapping.keyset_id), nullptr);
- OperationalKey * key = keyset.GetCurrentKey();
- if (nullptr != key)
+ Crypto::GroupOperationalCredentials * creds = keyset.GetCurrentGroupCredentials();
+ if (nullptr != creds)
{
- return mKeyContexPool.CreateObject(*this, ByteSpan(key->value, Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES),
- key->hash);
+ return mGroupKeyContexPool.CreateObject(
+ *this, ByteSpan(creds->encryption_key, Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES), creds->hash,
+ ByteSpan(creds->privacy_key, Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES));
}
}
}
@@ -1826,7 +1821,7 @@
if (key_idx < keyset.keys_count)
{
out_keyset.epoch_keys[key_idx].start_time = keyset.operational_keys[key_idx].start_time;
- memcpy(&out_keyset.epoch_keys[key_idx].key[0], keyset.operational_keys[key_idx].value, EpochKey::kLengthBytes);
+ memcpy(&out_keyset.epoch_keys[key_idx].key[0], keyset.operational_keys[key_idx].encryption_key, EpochKey::kLengthBytes);
}
}
@@ -1835,36 +1830,37 @@
void GroupDataProviderImpl::GroupKeyContext::Release()
{
- memset(mKeyValue, 0, sizeof(mKeyValue));
- mProvider.mKeyContexPool.ReleaseObject(this);
+ memset(mEncryptionKey, 0, sizeof(mEncryptionKey));
+ memset(mPrivacyKey, 0, sizeof(mPrivacyKey));
+ mProvider.mGroupKeyContexPool.ReleaseObject(this);
}
-CHIP_ERROR GroupDataProviderImpl::GroupKeyContext::EncryptMessage(const ByteSpan & plaintext, const ByteSpan & aad,
+CHIP_ERROR GroupDataProviderImpl::GroupKeyContext::MessageEncrypt(const ByteSpan & plaintext, const ByteSpan & aad,
const ByteSpan & nonce, MutableByteSpan & mic,
MutableByteSpan & ciphertext) const
{
uint8_t * output = ciphertext.data();
- return Crypto::AES_CCM_encrypt(plaintext.data(), plaintext.size(), aad.data(), aad.size(), mKeyValue,
+ return Crypto::AES_CCM_encrypt(plaintext.data(), plaintext.size(), aad.data(), aad.size(), mEncryptionKey,
Crypto::kAES_CCM128_Key_Length, nonce.data(), nonce.size(), output, mic.data(), mic.size());
}
-CHIP_ERROR GroupDataProviderImpl::GroupKeyContext::DecryptMessage(const ByteSpan & ciphertext, const ByteSpan & aad,
+CHIP_ERROR GroupDataProviderImpl::GroupKeyContext::MessageDecrypt(const ByteSpan & ciphertext, const ByteSpan & aad,
const ByteSpan & nonce, const ByteSpan & mic,
MutableByteSpan & plaintext) const
{
uint8_t * output = plaintext.data();
- return Crypto::AES_CCM_decrypt(ciphertext.data(), ciphertext.size(), aad.data(), aad.size(), mic.data(), mic.size(), mKeyValue,
- Crypto::kAES_CCM128_Key_Length, nonce.data(), nonce.size(), output);
+ return Crypto::AES_CCM_decrypt(ciphertext.data(), ciphertext.size(), aad.data(), aad.size(), mic.data(), mic.size(),
+ mEncryptionKey, Crypto::kAES_CCM128_Key_Length, nonce.data(), nonce.size(), output);
}
-CHIP_ERROR GroupDataProviderImpl::GroupKeyContext::EncryptPrivacy(MutableByteSpan & header, uint16_t session_id,
- const ByteSpan & payload, const ByteSpan & mic) const
+CHIP_ERROR GroupDataProviderImpl::GroupKeyContext::PrivacyEncrypt(const ByteSpan & input, const ByteSpan & nonce,
+ MutableByteSpan & output) const
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
-CHIP_ERROR GroupDataProviderImpl::GroupKeyContext::DecryptPrivacy(MutableByteSpan & header, uint16_t session_id,
- const ByteSpan & payload, const ByteSpan & mic) const
+CHIP_ERROR GroupDataProviderImpl::GroupKeyContext::PrivacyDecrypt(const ByteSpan & input, const ByteSpan & nonce,
+ MutableByteSpan & output) const
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
@@ -1876,7 +1872,7 @@
}
GroupDataProviderImpl::GroupSessionIteratorImpl::GroupSessionIteratorImpl(GroupDataProviderImpl & provider, uint16_t session_id) :
- mProvider(provider), mSessionId(session_id), mKeyContext(provider)
+ mProvider(provider), mSessionId(session_id), mGroupKeyContext(provider)
{
FabricList fabric_list;
ReturnOnFailure(fabric_list.Load(provider.mStorage));
@@ -1968,14 +1964,15 @@
continue;
}
- OperationalKey & key = keyset.operational_keys[mKeyIndex++];
- if (key.hash == mSessionId)
+ Crypto::GroupOperationalCredentials & creds = keyset.operational_keys[mKeyIndex++];
+ if (creds.hash == mSessionId)
{
- mKeyContext.SetKey(ByteSpan(key.value, sizeof(key.value)), mSessionId);
+ mGroupKeyContext.SetKey(ByteSpan(creds.encryption_key, sizeof(creds.encryption_key)), mSessionId);
+ mGroupKeyContext.SetPrivacyKey(ByteSpan(creds.privacy_key, sizeof(creds.privacy_key)));
output.fabric_index = fabric.fabric_index;
output.group_id = mapping.group_id;
output.security_policy = keyset.policy;
- output.key = &mKeyContext;
+ output.key = &mGroupKeyContext;
return true;
}
}
diff --git a/src/credentials/GroupDataProviderImpl.h b/src/credentials/GroupDataProviderImpl.h
index a67ce73..73c8bc2 100644
--- a/src/credentials/GroupDataProviderImpl.h
+++ b/src/credentials/GroupDataProviderImpl.h
@@ -152,34 +152,41 @@
public:
GroupKeyContext(GroupDataProviderImpl & provider) : mProvider(provider) {}
- GroupKeyContext(GroupDataProviderImpl & provider, const ByteSpan & key, uint16_t hash) : mProvider(provider)
+ GroupKeyContext(GroupDataProviderImpl & provider, const ByteSpan & encryptionKey, uint16_t hash,
+ const ByteSpan & privacyKey) :
+ mProvider(provider)
{
- SetKey(key, hash);
+ SetKey(encryptionKey, hash);
+ SetPrivacyKey(privacyKey);
}
- void SetKey(const ByteSpan & key, uint16_t hash)
+ void SetKey(const ByteSpan & encryptionKey, uint16_t hash)
{
mKeyHash = hash;
- memcpy(mKeyValue, key.data(), std::min(key.size(), sizeof(mKeyValue)));
+ memcpy(mEncryptionKey, encryptionKey.data(), std::min(encryptionKey.size(), sizeof(mEncryptionKey)));
+ }
+
+ void SetPrivacyKey(const ByteSpan & privacyKey)
+ {
+ memcpy(mPrivacyKey, privacyKey.data(), std::min(privacyKey.size(), sizeof(mPrivacyKey)));
}
uint16_t GetKeyHash() override { return mKeyHash; }
- CHIP_ERROR EncryptMessage(const ByteSpan & plaintext, const ByteSpan & aad, const ByteSpan & nonce, MutableByteSpan & mic,
+ CHIP_ERROR MessageEncrypt(const ByteSpan & plaintext, const ByteSpan & aad, const ByteSpan & nonce, MutableByteSpan & mic,
MutableByteSpan & ciphertext) const override;
- CHIP_ERROR DecryptMessage(const ByteSpan & ciphertext, const ByteSpan & aad, const ByteSpan & nonce, const ByteSpan & mic,
+ CHIP_ERROR MessageDecrypt(const ByteSpan & ciphertext, const ByteSpan & aad, const ByteSpan & nonce, const ByteSpan & mic,
MutableByteSpan & plaintext) const override;
- CHIP_ERROR EncryptPrivacy(MutableByteSpan & header, uint16_t session_id, const ByteSpan & payload,
- const ByteSpan & mic) const override;
- CHIP_ERROR DecryptPrivacy(MutableByteSpan & header, uint16_t session_id, const ByteSpan & payload,
- const ByteSpan & mic) const override;
+ CHIP_ERROR PrivacyEncrypt(const ByteSpan & input, const ByteSpan & nonce, MutableByteSpan & output) const override;
+ CHIP_ERROR PrivacyDecrypt(const ByteSpan & input, const ByteSpan & nonce, MutableByteSpan & output) const override;
void Release() override;
protected:
GroupDataProviderImpl & mProvider;
- uint16_t mKeyHash = 0;
- uint8_t mKeyValue[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES] = { 0 };
+ uint16_t mKeyHash = 0;
+ uint8_t mEncryptionKey[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES] = { 0 };
+ uint8_t mPrivacyKey[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES] = { 0 };
};
class KeySetIteratorImpl : public KeySetIterator
@@ -218,7 +225,7 @@
uint16_t mKeyIndex = 0;
uint16_t mKeyCount = 0;
bool mFirstMap = true;
- GroupKeyContext mKeyContext;
+ GroupKeyContext mGroupKeyContext;
};
bool IsInitialized() { return (mStorage != nullptr); }
CHIP_ERROR RemoveEndpoints(FabricIndex fabric_index, GroupId group_id);
@@ -229,7 +236,7 @@
ObjectPool<EndpointIteratorImpl, kIteratorsMax> mEndpointIterators;
ObjectPool<KeySetIteratorImpl, kIteratorsMax> mKeySetIterators;
ObjectPool<GroupSessionIteratorImpl, kIteratorsMax> mGroupSessionsIterator;
- ObjectPool<GroupKeyContext, kIteratorsMax> mKeyContexPool;
+ ObjectPool<GroupKeyContext, kIteratorsMax> mGroupKeyContexPool;
};
} // namespace Credentials
diff --git a/src/credentials/tests/TestGroupDataProvider.cpp b/src/credentials/tests/TestGroupDataProvider.cpp
index d2b59ee..c2aad08 100644
--- a/src/credentials/tests/TestGroupDataProvider.cpp
+++ b/src/credentials/tests/TestGroupDataProvider.cpp
@@ -1138,7 +1138,7 @@
NL_TEST_ASSERT(
apSuite,
CHIP_NO_ERROR ==
- key_context->EncryptMessage(plaintext, ByteSpan(aad, sizeof(aad)), ByteSpan(nonce, sizeof(nonce)), tag, ciphertext));
+ key_context->MessageEncrypt(plaintext, ByteSpan(aad, sizeof(aad)), ByteSpan(nonce, sizeof(nonce)), tag, ciphertext));
// The ciphertext must be different to the original message
NL_TEST_ASSERT(apSuite, memcmp(ciphertext.data(), kMessage, sizeof(kMessage)));
@@ -1169,7 +1169,7 @@
// Decrypt the ciphertext
NL_TEST_ASSERT(apSuite,
CHIP_NO_ERROR ==
- session.key->DecryptMessage(ciphertext, ByteSpan(aad, sizeof(aad)), ByteSpan(nonce, sizeof(nonce)),
+ session.key->MessageDecrypt(ciphertext, ByteSpan(aad, sizeof(aad)), ByteSpan(nonce, sizeof(nonce)),
tag, plaintext));
// The new plaintext must match the original message
diff --git a/src/crypto/CHIPCryptoPAL.cpp b/src/crypto/CHIPCryptoPAL.cpp
index eb0bb48..ec2e415 100644
--- a/src/crypto/CHIPCryptoPAL.cpp
+++ b/src/crypto/CHIPCryptoPAL.cpp
@@ -830,6 +830,19 @@
sizeof(kGroupPrivacyInfo), out_key.data(), Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES);
}
+CHIP_ERROR DeriveGroupOperationalCredentials(const ByteSpan & epoch_key, const ByteSpan & compressed_fabric_id,
+ GroupOperationalCredentials & operational_credentials)
+{
+ MutableByteSpan encryption_key(operational_credentials.encryption_key);
+ MutableByteSpan privacy_key(operational_credentials.privacy_key);
+
+ ReturnErrorOnFailure(Crypto::DeriveGroupOperationalKey(epoch_key, compressed_fabric_id, encryption_key));
+ ReturnErrorOnFailure(Crypto::DeriveGroupSessionId(encryption_key, operational_credentials.hash));
+ ReturnErrorOnFailure(Crypto::DeriveGroupPrivacyKey(encryption_key, privacy_key));
+
+ return CHIP_NO_ERROR;
+}
+
CHIP_ERROR ExtractVIDPIDFromAttributeString(DNAttrType attrType, const ByteSpan & attr,
AttestationCertVidPid & vidpidFromMatterAttr, AttestationCertVidPid & vidpidFromCNAttr)
{
diff --git a/src/crypto/CHIPCryptoPAL.h b/src/crypto/CHIPCryptoPAL.h
index 58d4845..cb18202 100644
--- a/src/crypto/CHIPCryptoPAL.h
+++ b/src/crypto/CHIPCryptoPAL.h
@@ -1485,6 +1485,21 @@
CHIP_ERROR ExtractVIDPIDFromX509Cert(const ByteSpan & x509Cert, AttestationCertVidPid & vidpid);
/**
+ * @brief The set of credentials needed to operate group message security with symmetric keys.
+ */
+typedef struct GroupOperationalCredentials
+{
+ /// Validity start time in microseconds since 2000-01-01T00:00:00 UTC ("the Epoch")
+ uint64_t start_time;
+ /// Session Id
+ uint16_t hash;
+ /// Operational group key
+ uint8_t encryption_key[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES];
+ /// Privacy key
+ uint8_t privacy_key[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES];
+} GroupOperationalCredentials;
+
+/**
* @brief Opaque context used to protect a symmetric key. The key operations must
* be performed without exposing the protected key value.
*/
@@ -1511,7 +1526,7 @@
* for ciphertext, and plaintext.
* @return CHIP_ERROR
*/
- virtual CHIP_ERROR EncryptMessage(const ByteSpan & plaintext, const ByteSpan & aad, const ByteSpan & nonce,
+ virtual CHIP_ERROR MessageEncrypt(const ByteSpan & plaintext, const ByteSpan & aad, const ByteSpan & nonce,
MutableByteSpan & mic, MutableByteSpan & ciphertext) const = 0;
/**
* @brief Perform the message decryption as described in 4.7.3.(Security Processing of Incoming Messages)
@@ -1523,30 +1538,26 @@
* for plaintext, and ciphertext.
* @return CHIP_ERROR
*/
- virtual CHIP_ERROR DecryptMessage(const ByteSpan & ciphertext, const ByteSpan & aad, const ByteSpan & nonce,
+ virtual CHIP_ERROR MessageDecrypt(const ByteSpan & ciphertext, const ByteSpan & aad, const ByteSpan & nonce,
const ByteSpan & mic, MutableByteSpan & plaintext) const = 0;
/**
* @brief Perform privacy encoding as described in 4.8.2. (Privacy Processing of Outgoing Messages)
- * @param[in,out] header Message header to encrypt
- * @param[in] session_id Outgoing SessionID
- * @param[in] payload Encrypted payload
- * @param[in] mic Outgoing Message Integrity Check
+ * @param[in] input Message header to privacy encrypt
+ * @param[in] nonce Privacy Nonce = session_id | mic
+ * @param[out] output Message header obfuscated
* @return CHIP_ERROR
*/
- virtual CHIP_ERROR EncryptPrivacy(MutableByteSpan & header, uint16_t session_id, const ByteSpan & payload,
- const ByteSpan & mic) const = 0;
+ virtual CHIP_ERROR PrivacyEncrypt(const ByteSpan & input, const ByteSpan & nonce, MutableByteSpan & output) const = 0;
/**
* @brief Perform privacy decoding as described in 4.8.3. (Privacy Processing of Incoming Messages)
- * @param[in,out] header Message header to decrypt
- * @param[in] session_id Incoming SessionID
- * @param[in] payload Encrypted payload
- * @param[in] mic Outgoing Message Integrity Check
+ * @param[in] input Message header to privacy decrypt
+ * @param[in] nonce Privacy Nonce = session_id | mic
+ * @param[out] output Message header deobfuscated
* @return CHIP_ERROR
*/
- virtual CHIP_ERROR DecryptPrivacy(MutableByteSpan & header, uint16_t session_id, const ByteSpan & payload,
- const ByteSpan & mic) const = 0;
+ virtual CHIP_ERROR PrivacyDecrypt(const ByteSpan & input, const ByteSpan & nonce, MutableByteSpan & output) const = 0;
/**
* @brief Release resources such as dynamic memory used to allocate this instance of the SymmetricKeyContext
@@ -1577,10 +1588,22 @@
* @brief Derives the Privacy Group Key using the Key Derivation Function (KDF) from the given epoch key.
* @param[in] epoch_key The epoch key. Must be CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES bytes length.
* @param[out] out_key Symmetric key used as the privacy key during message processing for group communication.
- The buffer size must be at least CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES bytes length.
+ * The buffer size must be at least CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES bytes length.
* @return Returns a CHIP_NO_ERROR on succcess, or CHIP_ERROR_INTERNAL if the provided key is invalid.
**/
CHIP_ERROR DeriveGroupPrivacyKey(const ByteSpan & epoch_key, MutableByteSpan & out_key);
+/**
+ * @brief Derives the complete set of credentials needed for group security.
+ *
+ * This function will derive the Encryption Key, Group Key Hash (Session Id), and Privacy Key
+ * for the given Epoch Key and Compressed Fabric Id.
+ * @param[in] epoch_key The epoch key. Must be CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES bytes length.
+ * @param[in] compressed_fabric_id The compressed fabric ID for the fabric (big endian byte string)
+ * @param[out] operational_credentials The set of Symmetric keys used during message processing for group communication.
+ * @return Returns a CHIP_NO_ERROR on succcess, or CHIP_ERROR_INTERNAL if the provided key is invalid.
+ **/
+CHIP_ERROR DeriveGroupOperationalCredentials(const ByteSpan & epoch_key, const ByteSpan & compressed_fabric_id,
+ GroupOperationalCredentials & operational_credentials);
} // namespace Crypto
} // namespace chip
diff --git a/src/crypto/tests/BUILD.gn b/src/crypto/tests/BUILD.gn
index 430540a..fac74ed 100644
--- a/src/crypto/tests/BUILD.gn
+++ b/src/crypto/tests/BUILD.gn
@@ -43,7 +43,10 @@
"TestCryptoLayer.h",
]
- test_sources = [ "TestPersistentStorageOpKeyStore.cpp" ]
+ test_sources = [
+ "TestGroupOperationalCredentials.cpp",
+ "TestPersistentStorageOpKeyStore.cpp",
+ ]
if (chip_device_platform == "esp32" || chip_device_platform == "nrfconnect" ||
chip_device_platform == "efr32") {
diff --git a/src/crypto/tests/TestGroupOperationalCredentials.cpp b/src/crypto/tests/TestGroupOperationalCredentials.cpp
new file mode 100644
index 0000000..b7493cc
--- /dev/null
+++ b/src/crypto/tests/TestGroupOperationalCredentials.cpp
@@ -0,0 +1,141 @@
+/*
+ *
+ * Copyright (c) 2022 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.
+ */
+
+#include <inttypes.h>
+
+#include <crypto/CHIPCryptoPAL.h>
+#include <lib/core/CHIPCore.h>
+#include <lib/support/CHIPMem.h>
+#include <lib/support/UnitTestRegistration.h>
+
+#include <nlunit-test.h>
+
+using namespace chip;
+using namespace chip::Crypto;
+
+namespace {
+
+constexpr auto KEY_LENGTH = Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES;
+
+// kFabricId1/kCompressedFabricIdBuffer1 matches the Compressed Fabric Identifier
+// example of spec section `4.3.2.2. Compressed Fabric Identifier`. It is based on
+// the public key in `kExampleOperationalRootPublicKey`.
+static const uint8_t kCompressedFabricIdBuffer1[] = { 0x87, 0xe1, 0xb0, 0x04, 0xe2, 0x35, 0xa1, 0x30 };
+constexpr ByteSpan kCompressedFabricId1(kCompressedFabricIdBuffer1);
+
+static const uint8_t kEpochKeys0[][KEY_LENGTH] = {
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f },
+ { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f }
+};
+
+static GroupOperationalCredentials kGroupKeys0[] = {
+ { .start_time = 0x0000000000000000,
+ .hash = 0x479e,
+ .encryption_key = { 0xc5, 0xf2, 0x69, 0x01, 0x87, 0x11, 0x51, 0x50, 0xc3, 0x56, 0xad, 0x93, 0xb3, 0x85, 0xbb, 0x0f },
+ .privacy_key = { 0xf5, 0xce, 0x81, 0x88, 0x1d, 0x11, 0x18, 0xc5, 0xc8, 0x91, 0xc9, 0x06, 0x0b, 0xc7, 0x70, 0x09 } },
+ { .start_time = 0x1111111111111111,
+ .hash = 0xa512,
+ .encryption_key = { 0xae, 0xd9, 0x56, 0x95, 0xf3, 0x75, 0xd2, 0xce, 0x78, 0x55, 0x6a, 0x41, 0x73, 0x0c, 0x3f, 0x43 },
+ .privacy_key = { 0xdc, 0x90, 0xdb, 0x2e, 0x61, 0x76, 0x5a, 0x42, 0x60, 0x7f, 0xaf, 0xaf, 0x16, 0x37, 0x40, 0x0a } },
+ { .start_time = 0x2222222222222222,
+ .hash = 0xd800,
+ .encryption_key = { 0x35, 0xca, 0x34, 0x6e, 0x5e, 0x24, 0xbb, 0xbe, 0x88, 0x9c, 0xf4, 0xd3, 0x5c, 0x5e, 0x82, 0x0a },
+ .privacy_key = { 0xb1, 0xfb, 0x3e, 0x79, 0xa2, 0xff, 0x27, 0xf3, 0x70, 0x4d, 0x2b, 0xe9, 0x19, 0x2d, 0xb6, 0x07 } },
+};
+
+struct GroupKeySetTestEntry
+{
+ const uint8_t * epochKey; ///< Raw epoch key
+ GroupOperationalCredentials * groupKeys; ///< Struct of encryption key, privacy key, and group key hash (sessionId)
+};
+
+struct GroupKeySetTestEntry theGroupKeySetTestVector[] = {
+ {
+ .epochKey = kEpochKeys0[0],
+ .groupKeys = &kGroupKeys0[0],
+ },
+ {
+ .epochKey = kEpochKeys0[1],
+ .groupKeys = &kGroupKeys0[1],
+ },
+ {
+ .epochKey = kEpochKeys0[2],
+ .groupKeys = &kGroupKeys0[2],
+ },
+};
+
+const uint16_t theGroupKeySetTestVectorLength = sizeof(theGroupKeySetTestVector) / sizeof(theGroupKeySetTestVector[0]);
+
+void TestDeriveGroupOperationalCredentials(nlTestSuite * apSuite, void * apContext)
+{
+ GroupOperationalCredentials opCreds;
+
+ for (unsigned i = 0; i < theGroupKeySetTestVectorLength; i++)
+ {
+ const ByteSpan epochKey(theGroupKeySetTestVector[i].epochKey, KEY_LENGTH);
+ NL_TEST_ASSERT(apSuite,
+ CHIP_NO_ERROR == Crypto::DeriveGroupOperationalCredentials(epochKey, kCompressedFabricId1, opCreds));
+
+ NL_TEST_ASSERT(apSuite, opCreds.hash == theGroupKeySetTestVector[i].groupKeys->hash);
+ NL_TEST_ASSERT(apSuite,
+ 0 == memcmp(opCreds.encryption_key, theGroupKeySetTestVector[i].groupKeys->encryption_key, KEY_LENGTH));
+ NL_TEST_ASSERT(apSuite, 0 == memcmp(opCreds.privacy_key, theGroupKeySetTestVector[i].groupKeys->privacy_key, KEY_LENGTH));
+ }
+}
+
+/**
+ * Test Suite. It lists all the test functions.
+ */
+const nlTest sTests[] = { NL_TEST_DEF("TestDeriveGroupOperationalCredentials", TestDeriveGroupOperationalCredentials),
+ NL_TEST_SENTINEL() };
+
+/**
+ * Set up the test suite.
+ */
+int Test_Setup(void * inContext)
+{
+ CHIP_ERROR error = chip::Platform::MemoryInit();
+ VerifyOrReturnError(error == CHIP_NO_ERROR, FAILURE);
+ return SUCCESS;
+}
+
+/**
+ * Tear down the test suite.
+ */
+int Test_Teardown(void * inContext)
+{
+ chip::Platform::MemoryShutdown();
+ return SUCCESS;
+}
+
+} // namespace
+
+/**
+ * Main
+ */
+int TestGroupOperationalCredentials()
+{
+ nlTestSuite theSuite = { "TestGroupOperationalCredentials", &sTests[0], Test_Setup, Test_Teardown };
+
+ // Run test suite againt one context.
+ nlTestRunner(&theSuite, nullptr);
+ return nlTestRunnerStats(&theSuite);
+}
+
+CHIP_REGISTER_TEST_SUITE(TestGroupOperationalCredentials)
diff --git a/src/transport/CryptoContext.cpp b/src/transport/CryptoContext.cpp
index 1150f7a..98c448a 100644
--- a/src/transport/CryptoContext.cpp
+++ b/src/transport/CryptoContext.cpp
@@ -147,6 +147,16 @@
return bbuf.Fit() ? CHIP_NO_ERROR : CHIP_ERROR_NO_MEMORY;
}
+CHIP_ERROR CryptoContext::BuildPrivacyNonce(NonceView nonce, uint16_t sessionId, const MessageAuthenticationCode & mac)
+{
+ const uint8_t * micFragment = &mac.GetTag()[kPrivacyNonceMicFragmentOffset];
+ Encoding::BigEndian::BufferWriter bbuf(nonce.data(), nonce.size());
+
+ bbuf.Put16(sessionId);
+ bbuf.Put(micFragment, kPrivacyNonceMicFragmentLength);
+ return bbuf.Fit() ? CHIP_NO_ERROR : CHIP_ERROR_NO_MEMORY;
+}
+
CHIP_ERROR CryptoContext::GetAdditionalAuthData(const PacketHeader & header, uint8_t * aad, uint16_t & len)
{
VerifyOrReturnError(len >= header.EncodeSizeBytes(), CHIP_ERROR_INVALID_ARGUMENT);
@@ -187,7 +197,7 @@
MutableByteSpan ciphertext(output, input_length);
MutableByteSpan mic(tag, taglen);
- ReturnErrorOnFailure(mKeyContext->EncryptMessage(plaintext, ByteSpan(AAD, aadLen), nonce, mic, ciphertext));
+ ReturnErrorOnFailure(mKeyContext->MessageEncrypt(plaintext, ByteSpan(AAD, aadLen), nonce, mic, ciphertext));
}
else
{
@@ -231,7 +241,7 @@
MutableByteSpan plaintext(output, input_length);
ByteSpan mic(tag, taglen);
- CHIP_ERROR err = mKeyContext->DecryptMessage(ciphertext, ByteSpan(AAD, aadLen), nonce, mic, plaintext);
+ CHIP_ERROR err = mKeyContext->MessageDecrypt(ciphertext, ByteSpan(AAD, aadLen), nonce, mic, plaintext);
ReturnErrorOnFailure(err);
}
else
@@ -253,4 +263,40 @@
return CHIP_NO_ERROR;
}
+CHIP_ERROR CryptoContext::PrivacyEncrypt(const uint8_t * input, size_t input_length, uint8_t * output, PacketHeader & header,
+ MessageAuthenticationCode & mac) const
+{
+ VerifyOrReturnError(input != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+ VerifyOrReturnError(input_length > 0, CHIP_ERROR_INVALID_ARGUMENT);
+ VerifyOrReturnError(output != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+
+ // Confirm group key is available. Privacy obfuscation is not supported on unicast session keys.
+ VerifyOrReturnError(mKeyContext != nullptr, CHIP_ERROR_INVALID_USE_OF_SESSION_KEY);
+
+ ByteSpan plaintext(input, input_length);
+ MutableByteSpan privacytext(output, input_length);
+ CryptoContext::NonceStorage privacyNonce;
+ CryptoContext::BuildPrivacyNonce(privacyNonce, header.GetSessionId(), mac);
+
+ return mKeyContext->PrivacyEncrypt(plaintext, privacyNonce, privacytext);
+}
+
+CHIP_ERROR CryptoContext::PrivacyDecrypt(const uint8_t * input, size_t input_length, uint8_t * output, const PacketHeader & header,
+ const MessageAuthenticationCode & mac) const
+{
+ VerifyOrReturnError(input != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+ VerifyOrReturnError(input_length > 0, CHIP_ERROR_INVALID_ARGUMENT);
+ VerifyOrReturnError(output != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+
+ // Confirm group key is available. Privacy obfuscation is not supported on session keys.
+ VerifyOrReturnError(mKeyContext != nullptr, CHIP_ERROR_INVALID_USE_OF_SESSION_KEY);
+
+ const ByteSpan privacytext(input, input_length);
+ MutableByteSpan plaintext(output, input_length);
+ CryptoContext::NonceStorage privacyNonce;
+ CryptoContext::BuildPrivacyNonce(privacyNonce, header.GetSessionId(), mac);
+
+ return mKeyContext->PrivacyDecrypt(privacytext, privacyNonce, plaintext);
+}
+
} // namespace chip
diff --git a/src/transport/CryptoContext.h b/src/transport/CryptoContext.h
index 0aeae73..9ab7b13 100644
--- a/src/transport/CryptoContext.h
+++ b/src/transport/CryptoContext.h
@@ -35,10 +35,12 @@
class DLL_EXPORT CryptoContext
{
public:
- static constexpr size_t kAESCCMNonceLen = 13;
- using NonceStorage = std::array<uint8_t, kAESCCMNonceLen>;
- using NonceView = FixedSpan<uint8_t, kAESCCMNonceLen>;
- using ConstNonceView = FixedSpan<const uint8_t, kAESCCMNonceLen>;
+ static constexpr size_t kPrivacyNonceMicFragmentOffset = 5;
+ static constexpr size_t kPrivacyNonceMicFragmentLength = 11;
+ static constexpr size_t kAESCCMNonceLen = 13;
+ using NonceStorage = std::array<uint8_t, kAESCCMNonceLen>;
+ using NonceView = FixedSpan<uint8_t, kAESCCMNonceLen>;
+ using ConstNonceView = FixedSpan<const uint8_t, kAESCCMNonceLen>;
CryptoContext();
~CryptoContext();
@@ -94,6 +96,9 @@
/** @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);
+ /** @brief Build a Nonce buffer using given parameters for encrypt or decrypt. */
+ static CHIP_ERROR BuildPrivacyNonce(NonceView nonce, uint16_t sessionId, const MessageAuthenticationCode & mac);
+
/**
* @brief
* Encrypt the input data using keys established in the secure channel
@@ -125,6 +130,12 @@
CHIP_ERROR Decrypt(const uint8_t * input, size_t input_length, uint8_t * output, ConstNonceView nonce,
const PacketHeader & header, const MessageAuthenticationCode & mac) const;
+ CHIP_ERROR PrivacyEncrypt(const uint8_t * input, size_t input_length, uint8_t * output, PacketHeader & header,
+ MessageAuthenticationCode & mac) const;
+
+ CHIP_ERROR PrivacyDecrypt(const uint8_t * input, size_t input_length, uint8_t * output, const PacketHeader & header,
+ const MessageAuthenticationCode & mac) const;
+
ByteSpan GetAttestationChallenge() const { return ByteSpan(mKeys[kAttestationChallengeKey], Crypto::kAES_CCM128_Key_Length); }
/**
diff --git a/src/transport/tests/BUILD.gn b/src/transport/tests/BUILD.gn
index 26087e6..ce7579b 100644
--- a/src/transport/tests/BUILD.gn
+++ b/src/transport/tests/BUILD.gn
@@ -33,6 +33,7 @@
output_name = "libTransportLayerTests"
test_sources = [
+ "TestCryptoContext.cpp",
"TestGroupMessageCounter.cpp",
"TestPeerConnections.cpp",
"TestPeerMessageCounter.cpp",
diff --git a/src/transport/tests/TestCryptoContext.cpp b/src/transport/tests/TestCryptoContext.cpp
new file mode 100644
index 0000000..a88bace
--- /dev/null
+++ b/src/transport/tests/TestCryptoContext.cpp
@@ -0,0 +1,108 @@
+/*
+ *
+ * Copyright (c) 2022 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.
+ */
+
+#include <inttypes.h>
+#include <nlunit-test.h>
+
+#include <crypto/CHIPCryptoPAL.h>
+#include <lib/core/CHIPCore.h>
+#include <lib/support/CHIPMem.h>
+#include <lib/support/UnitTestRegistration.h>
+
+#include <transport/CryptoContext.h>
+
+using namespace chip;
+
+namespace {
+
+constexpr size_t MIC_LENGTH = chip::Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES;
+constexpr size_t NONCE_LENGTH = CryptoContext::kAESCCMNonceLen;
+
+struct PrivacyNonceTestEntry
+{
+ uint16_t sessionId;
+ uint8_t mic[MIC_LENGTH];
+ uint8_t privacyNonce[NONCE_LENGTH];
+};
+
+struct PrivacyNonceTestEntry thePrivacyNonceTestVector[] = {
+ {
+ .sessionId = 0x002a,
+ .mic = { 0xc5, 0xa0, 0x06, 0x3a, 0xd5, 0xd2, 0x51, 0x81, 0x91, 0x40, 0x0d, 0xd6, 0x8c, 0x5c, 0x16, 0x3b },
+ .privacyNonce = { 0x00, 0x2a, 0xd2, 0x51, 0x81, 0x91, 0x40, 0x0d, 0xd6, 0x8c, 0x5c, 0x16, 0x3b },
+ },
+};
+
+const uint16_t thePrivacyNonceTestVectorLength = sizeof(thePrivacyNonceTestVector) / sizeof(thePrivacyNonceTestVector[0]);
+
+void TestBuildPrivacyNonce(nlTestSuite * apSuite, void * apContext)
+{
+ for (unsigned i = 0; i < thePrivacyNonceTestVectorLength; i++)
+ {
+ MessageAuthenticationCode mic;
+ uint16_t sessionId = thePrivacyNonceTestVector[i].sessionId;
+ const ByteSpan expectedPrivacyNonce(thePrivacyNonceTestVector[i].privacyNonce, NONCE_LENGTH);
+ CryptoContext::NonceStorage privacyNonce;
+ CryptoContext::ConstNonceView privacyNonceView(privacyNonce);
+
+ mic.SetTag(nullptr, thePrivacyNonceTestVector[i].mic, MIC_LENGTH);
+
+ NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == chip::CryptoContext::BuildPrivacyNonce(privacyNonce, sessionId, mic));
+ NL_TEST_ASSERT(apSuite, 0 == memcmp(privacyNonceView.data(), expectedPrivacyNonce.data(), NONCE_LENGTH));
+ }
+}
+
+/**
+ * Test Suite. It lists all the test functions.
+ */
+const nlTest sTests[] = { NL_TEST_DEF("TestBuildPrivacyNonce", TestBuildPrivacyNonce), NL_TEST_SENTINEL() };
+
+/**
+ * Set up the test suite.
+ */
+int Test_Setup(void * inContext)
+{
+ CHIP_ERROR error = chip::Platform::MemoryInit();
+ VerifyOrReturnError(error == CHIP_NO_ERROR, FAILURE);
+ return SUCCESS;
+}
+
+/**
+ * Tear down the test suite.
+ */
+int Test_Teardown(void * inContext)
+{
+ chip::Platform::MemoryShutdown();
+ return SUCCESS;
+}
+
+} // namespace
+
+/**
+ * Main
+ */
+int TestGroupCryptoContext()
+{
+ nlTestSuite theSuite = { "TestGroupCryptoContext", &sTests[0], Test_Setup, Test_Teardown };
+
+ // Run test suite againt one context.
+ nlTestRunner(&theSuite, nullptr);
+ return nlTestRunnerStats(&theSuite);
+}
+
+CHIP_REGISTER_TEST_SUITE(TestGroupCryptoContext)