Skip to content

Commit ad69154

Browse files
crypto: add cryptohelper unit tests
Signed-off-by: Mykola Kobets <[email protected]>
1 parent 0a40f82 commit ad69154

File tree

3 files changed

+767
-1
lines changed

3 files changed

+767
-1
lines changed

src/core/common/crypto/tests/CMakeLists.txt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ set(TARGET_NAME crypto_test)
1313
set(SOURCES crypto.cpp)
1414

1515
if(WITH_MBEDTLS OR WITH_OPENSSL)
16-
list(APPEND SOURCES cryptoprovider.cpp cryptoutils.cpp)
16+
list(APPEND SOURCES cryptohelper.cpp cryptoprovider.cpp cryptoutils.cpp)
1717
endif()
1818

1919
# ######################################################################################################################
@@ -35,3 +35,38 @@ add_test(
3535
LIBRARIES
3636
${LIBRARIES}
3737
)
38+
39+
######################################################################################################################
40+
# Gen certificates
41+
# ######################################################################################################################
42+
43+
include(gencertificates.cmake)
44+
45+
message("\n\n==================================================")
46+
message("Generating certificates for CryptoProvider tests...")
47+
message("==================================================")
48+
49+
gencertificates("${TARGET_PREFIX}_${TARGET_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/certificates")
50+
51+
message("\n\n==================================================")
52+
message("Generating certificates for CryptoHelper tests...")
53+
message("==================================================")
54+
55+
generate_cryptohelper_test_certs("${TARGET_PREFIX}_${TARGET_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/cryptohelper")
56+
57+
cryptohelper_generate_test_aes(
58+
"${TARGET_PREFIX}_${TARGET_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/cryptohelper/aes"
59+
"${CMAKE_CURRENT_BINARY_DIR}/cryptohelper/offline1.pem"
60+
)
61+
cryptohelper_generate_encrypted_test_file(
62+
"${CMAKE_CURRENT_BINARY_DIR}/cryptohelper/aes/aes.key"
63+
"${CMAKE_CURRENT_BINARY_DIR}/cryptohelper/aes/hello-world.txt"
64+
)
65+
66+
cryptohelper_sign_file("${CMAKE_CURRENT_BINARY_DIR}/cryptohelper" "hello-world.txt" "online")
67+
cryptohelper_sign_file("${CMAKE_CURRENT_BINARY_DIR}/cryptohelper" "hello-world.txt" "offline1")
68+
cryptohelper_sign_file("${CMAKE_CURRENT_BINARY_DIR}/cryptohelper" "hello-world.txt" "offline2")
69+
cryptohelper_sign_file("${CMAKE_CURRENT_BINARY_DIR}/cryptohelper" "hello-world.txt" "onlineTest1")
70+
cryptohelper_sign_file("${CMAKE_CURRENT_BINARY_DIR}/cryptohelper" "hello-world.txt" "onlineTest2")
71+
72+
cryptohelper_create_cms("${CMAKE_CURRENT_BINARY_DIR}/cryptohelper" "hello-world-cms.txt" "offline1")
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/*
2+
* Copyright (C) 2025 EPAM Systems, Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <gmock/gmock.h>
8+
9+
#include <map>
10+
#include <string>
11+
#include <vector>
12+
13+
#include <core/common/cloudprotocol/servicediscovery.hpp>
14+
#include <core/common/crypto/cryptohelper.hpp>
15+
#include <core/common/tests/crypto/providers/cryptofactory.hpp>
16+
#include <core/common/tests/crypto/softhsmenv.hpp>
17+
#include <core/common/tests/utils/log.hpp>
18+
#include <core/common/tools/utils.hpp>
19+
20+
using namespace testing;
21+
22+
namespace aos::crypto {
23+
24+
/***********************************************************************************************************************
25+
* Test stubs
26+
**********************************************************************************************************************/
27+
28+
/**
29+
* Stub implementation of CertProviderItf.
30+
*/
31+
class CertProviderStub : public iam::certhandler::CertProviderItf {
32+
public:
33+
Error GetCert(const String& certType, const Array<uint8_t>& issuer, const Array<uint8_t>& serial,
34+
iam::certhandler::CertInfo& resCert) const override
35+
{
36+
(void)issuer;
37+
(void)serial;
38+
39+
if (mCerts.count(certType.CStr()) == 0) {
40+
return ErrorEnum::eNotFound;
41+
}
42+
43+
resCert = mCerts.find(certType.CStr())->second;
44+
45+
return ErrorEnum::eNone;
46+
}
47+
48+
Error SubscribeCertChanged(const String& certType, iam::certhandler::CertReceiverItf& certReceiver) override
49+
{
50+
(void)certType;
51+
(void)certReceiver;
52+
53+
return ErrorEnum::eNone;
54+
}
55+
56+
Error UnsubscribeCertChanged(iam::certhandler::CertReceiverItf& certReceiver) override
57+
{
58+
(void)certReceiver;
59+
60+
return ErrorEnum::eNone;
61+
}
62+
63+
void AddCert(const std::string& certType, const std::string& certName)
64+
{
65+
iam::certhandler::CertInfo certInfo;
66+
67+
certInfo.mCertURL = ("file://" + FullCertPath(certName)).c_str();
68+
certInfo.mKeyURL = ("file://" + FullKeyPath(certName)).c_str();
69+
70+
mCerts[certType] = certInfo;
71+
}
72+
73+
private:
74+
static std::string FullCertPath(const std::string& name)
75+
{
76+
return std::string(CRYPTOHELPER_CERTS_DIR) + "/" + name + ".pem";
77+
}
78+
79+
static std::string FullKeyPath(const std::string& name)
80+
{
81+
return std::string(CRYPTOHELPER_CERTS_DIR) + "/" + name + ".key";
82+
}
83+
84+
std::map<std::string, iam::certhandler::CertInfo> mCerts;
85+
};
86+
87+
void AssertOK(const Error& err)
88+
{
89+
if (!err.IsNone()) {
90+
throw err;
91+
}
92+
}
93+
94+
std::vector<uint8_t> ReadFileFromAESDir(const String& fileName)
95+
{
96+
StaticArray<uint8_t, 2048> content;
97+
98+
auto fullPath = CRYPTOHELPER_AES_DIR "/" + std::string(fileName.CStr());
99+
100+
AssertOK(fs::ReadFile(fullPath.c_str(), content));
101+
102+
return {content.begin(), content.end()};
103+
}
104+
105+
std::vector<uint8_t> ReadFileFromCrtDir(const String& fileName)
106+
{
107+
StaticArray<uint8_t, 2048> content;
108+
109+
auto fullPath = CRYPTOHELPER_CERTS_DIR "/" + std::string(fileName.CStr());
110+
111+
AssertOK(fs::ReadFile(fullPath.c_str(), content));
112+
113+
return {content.begin(), content.end()};
114+
}
115+
116+
cloudprotocol::DecryptInfo CreateDecryptionInfo(
117+
const char* blockAlg, const std::vector<uint8_t>& blockIV, const std::vector<uint8_t>& blockKey)
118+
{
119+
cloudprotocol::DecryptInfo decryptInfo;
120+
121+
decryptInfo.mBlockAlg = blockAlg;
122+
decryptInfo.mBlockIV = Array<uint8_t>(blockIV.data(), blockIV.size());
123+
decryptInfo.mBlockIV.Resize(16);
124+
125+
decryptInfo.mBlockKey = Array<uint8_t>(blockKey.data(), blockKey.size());
126+
127+
return decryptInfo;
128+
}
129+
130+
cloudprotocol::CertificateInfo CreateCert(CryptoProviderItf& provider, const char* name)
131+
{
132+
auto fullPath = CRYPTOHELPER_CERTS_DIR "/" + std::string(name) + ".pem";
133+
134+
StaticString<cCertPEMLen> pem;
135+
x509::CertificateChain chain;
136+
137+
AssertOK(fs::ReadFileToString(fullPath.c_str(), pem));
138+
AssertOK(provider.PEMToX509Certs(pem, chain));
139+
140+
cloudprotocol::CertificateInfo cert;
141+
142+
cert.mFingerprint = name;
143+
cert.mCertificate = chain[0].mRaw;
144+
145+
return cert;
146+
}
147+
148+
cloudprotocol::CertificateChainInfo CreateCertChain(const char* name, const std::vector<std::string> fingerprints)
149+
{
150+
cloudprotocol::CertificateChainInfo chain;
151+
152+
chain.mName = name;
153+
154+
for (const auto& item : fingerprints) {
155+
AssertOK(chain.mFingerprints.PushBack(item.c_str()));
156+
}
157+
158+
return chain;
159+
}
160+
161+
cloudprotocol::SignInfo CreateSigns(const char* chainName, const char* algName)
162+
{
163+
cloudprotocol::SignInfo signs;
164+
165+
signs.mChainName = chainName;
166+
signs.mAlg = algName;
167+
168+
auto testFilePath = std::string(CRYPTOHELPER_CERTS_DIR "/hello-world.txt.") + chainName + ".sig";
169+
AssertOK(fs::ReadFile(testFilePath.c_str(), signs.mValue));
170+
signs.mTrustedTimestamp = Time::Now();
171+
172+
return signs;
173+
}
174+
175+
/***********************************************************************************************************************
176+
* Suite
177+
**********************************************************************************************************************/
178+
179+
class CryptoHelperTest : public Test {
180+
public:
181+
void SetUp() override
182+
{
183+
tests::utils::InitLog();
184+
185+
ASSERT_TRUE(mCryptoFactory.Init().IsNone());
186+
187+
mCryptoProvider = &mCryptoFactory.GetCryptoProvider();
188+
189+
ASSERT_TRUE(mSoftHSMEnv.Init(cPIN, cLabel).IsNone());
190+
ASSERT_TRUE(mCertLoader.Init(*mCryptoProvider, mSoftHSMEnv.GetManager()).IsNone());
191+
192+
ASSERT_TRUE(
193+
mCryptoHelper.Init(mCertProvider, *mCryptoProvider, mCertLoader, cDefaultServiceDiscoveryURL, cCACert)
194+
.IsNone());
195+
}
196+
197+
protected:
198+
static constexpr auto cDefaultServiceDiscoveryURL = "http://service-discovery-url.html";
199+
static constexpr auto cCACert = CRYPTOHELPER_CERTS_DIR "/rootCA.pem";
200+
201+
static constexpr auto cLabel = "iam pkcs11 test slot";
202+
static constexpr auto cPIN = "admin";
203+
204+
DefaultCryptoFactory mCryptoFactory;
205+
206+
CryptoProviderItf* mCryptoProvider;
207+
208+
CertProviderStub mCertProvider;
209+
CertLoader mCertLoader;
210+
CryptoHelper mCryptoHelper;
211+
test::SoftHSMEnv mSoftHSMEnv;
212+
};
213+
214+
TEST_F(CryptoHelperTest, ServiceDiscoveryURLs)
215+
{
216+
struct TestData {
217+
std::string mCert;
218+
std::string mServiceDiscoveryURL;
219+
};
220+
221+
TestData testData[] = {{"online", "https://www.mytest.com"}, {"onlineTest1", "https://Test1:9000"},
222+
{"onlineTest2", cDefaultServiceDiscoveryURL}};
223+
224+
for (const auto& [certName, url] : testData) {
225+
mCertProvider.AddCert("online", certName);
226+
227+
StaticArray<StaticString<cURLLen>, cloudprotocol::cURLsCount> discoveryURLs;
228+
229+
ASSERT_TRUE(mCryptoHelper.GetServiceDiscoveryURLs(discoveryURLs).IsNone());
230+
ASSERT_EQ(discoveryURLs.Size(), 1);
231+
EXPECT_EQ(url, discoveryURLs[0].CStr());
232+
}
233+
}
234+
235+
TEST_F(CryptoHelperTest, Decrypt)
236+
{
237+
struct TestData {
238+
const char* mEncryptedFile;
239+
cloudprotocol::DecryptInfo mDecryptInfo;
240+
std::vector<uint8_t> mDecryptedContent;
241+
};
242+
243+
std::vector<TestData> testData = {
244+
245+
{CRYPTOHELPER_AES_DIR "/hello-world.txt.enc",
246+
CreateDecryptionInfo("AES256/CBC/PKCS7PADDING", {1, 2, 3, 4, 5}, ReadFileFromAESDir("aes.key")),
247+
ReadFileFromAESDir("hello-world.txt")},
248+
};
249+
250+
mCertProvider.AddCert("offline", "offline1");
251+
252+
for (const auto& test : testData) {
253+
LOG_INF() << "Decode encrypted file: " << test.mEncryptedFile;
254+
255+
constexpr auto cDecryptedFile = CRYPTOHELPER_AES_DIR "/decrypted.raw";
256+
257+
ASSERT_TRUE(mCryptoHelper.Decrypt(test.mEncryptedFile, cDecryptedFile, test.mDecryptInfo).IsNone());
258+
EXPECT_EQ(test.mDecryptedContent, ReadFileFromAESDir("decrypted.raw"));
259+
}
260+
}
261+
262+
TEST_F(CryptoHelperTest, ValidateSigns)
263+
{
264+
struct TestData {
265+
std::vector<cloudprotocol::CertificateInfo> mCerts;
266+
cloudprotocol::CertificateChainInfo mChain;
267+
cloudprotocol::SignInfo mSigns;
268+
};
269+
270+
constexpr auto cDecryptedFile = CRYPTOHELPER_CERTS_DIR "/hello-world.txt";
271+
272+
auto rootCA = CreateCert(*mCryptoProvider, "rootCA");
273+
auto secondaryCA = CreateCert(*mCryptoProvider, "secondaryCA");
274+
auto intermediateCA = CreateCert(*mCryptoProvider, "intermediateCA");
275+
276+
auto online = CreateCert(*mCryptoProvider, "online");
277+
auto offline1 = CreateCert(*mCryptoProvider, "offline1");
278+
auto offline2 = CreateCert(*mCryptoProvider, "offline2");
279+
auto onlineTest1 = CreateCert(*mCryptoProvider, "onlineTest1");
280+
auto onlineTest2 = CreateCert(*mCryptoProvider, "onlineTest2");
281+
282+
std::vector<TestData> testData = {
283+
{{online, intermediateCA, secondaryCA}, CreateCertChain("online", {"online", "intermediateCA", "secondaryCA"}),
284+
CreateSigns("online", "RSA/SHA256/PKCS1v1_5")},
285+
286+
{{offline1, intermediateCA, secondaryCA},
287+
CreateCertChain("offline1", {"offline1", "intermediateCA", "secondaryCA"}),
288+
CreateSigns("offline1", "RSA/SHA256/PKCS1v1_5")},
289+
{{offline2, intermediateCA, secondaryCA},
290+
CreateCertChain("offline2", {"offline2", "intermediateCA", "secondaryCA"}),
291+
CreateSigns("offline2", "RSA/SHA256/PKCS1v1_5")},
292+
{{onlineTest1, intermediateCA, secondaryCA},
293+
CreateCertChain("onlineTest1", {"onlineTest1", "intermediateCA", "secondaryCA"}),
294+
CreateSigns("onlineTest1", "RSA/SHA256/PKCS1v1_5")},
295+
{{onlineTest2, intermediateCA, secondaryCA},
296+
CreateCertChain("onlineTest2", {"onlineTest2", "intermediateCA", "secondaryCA"}),
297+
CreateSigns("onlineTest2", "RSA/SHA256/PKCS1v1_5")}};
298+
299+
for (const auto& item : testData) {
300+
StaticArray<cloudprotocol::CertificateInfo, 10> certs;
301+
certs = Array<cloudprotocol::CertificateInfo>(item.mCerts.data(), item.mCerts.size());
302+
303+
StaticArray<cloudprotocol::CertificateChainInfo, 1> chains;
304+
chains.PushBack(item.mChain);
305+
306+
ASSERT_TRUE(mCryptoHelper.ValidateSigns(cDecryptedFile, item.mSigns, chains, certs).IsNone());
307+
}
308+
}
309+
310+
TEST_F(CryptoHelperTest, DecryptMetadata)
311+
{
312+
StaticArray<uint8_t, cCloudMetadataSize> output;
313+
314+
auto inputData = ReadFileFromCrtDir("hello-world-cms.txt.offline1.cms");
315+
auto input = Array<uint8_t>(inputData.data(), inputData.size());
316+
317+
mCertProvider.AddCert("offline", "offline1");
318+
319+
ASSERT_TRUE(mCryptoHelper.DecryptMetadata(input, output).IsNone());
320+
321+
auto expected = ReadFileFromCrtDir("hello-world-cms.txt");
322+
EXPECT_EQ(std::vector<uint8_t>(output.begin(), output.end()), expected);
323+
}
324+
325+
} // namespace aos::crypto

0 commit comments

Comments
 (0)