1 /*!
2  * \copyright Copyright (c) 2018-2021 Governikus GmbH & Co. KG, Germany
3  */
4 
5 #include "messages/IfdEstablishPaceChannel.h"
6 
7 #include "LogHandler.h"
8 #include "TestFileHelper.h"
9 
10 #include <QtTest>
11 
12 
13 using namespace governikus;
14 
15 
16 Q_DECLARE_METATYPE(IfdVersion::Version)
17 
18 
19 class test_IfdEstablishPaceChannel
20 	: public QObject
21 {
22 	Q_OBJECT
23 
24 	const QByteArray mChatHex = "7F4C12060904007F00070301020253050000000F0F";
25 	const QByteArray mCertHex = "30 8202A4"
26 								"06 0A 04007F00070301030103"
27 								"A1 0E 0C0C442D547275737420476D6248"
28 								"A3 3A 0C38476573616D7476657262616E64206465722064657574736368656E20566572736963686572756E67737769727473636861667420652E562E"
29 								"A5 820248"
30 								"04 820244 4E616D652C20416E7363687269667420756E6420452D4D61696C2D4164726573736520646573204469656E737465616E626965746572733A0D0A476573616D7476657262616E64206465722064657574736368656E20566572736963686572756E67737769727473636861667420652E562E0D0A57696C68656C6D73747261C39F652034332F3433670D0A3130313137204265726C696E0D0A6265726C696E406764762E64650D0A0D0A4765736368C3A46674737A7765636B3A0D0A2D52656769737472696572756E6720756E64204C6F67696E20616D204744562D4D616B6C6572706F7274616C2D0D0A0D0A48696E7765697320617566206469652066C3BC722064656E204469656E737465616E626965746572207A757374C3A46E646967656E205374656C6C656E2C20646965206469652045696E68616C74756E672064657220566F7273636872696674656E207A756D20446174656E73636875747A206B6F6E74726F6C6C696572656E3A0D0A4265726C696E6572204265617566747261677465722066C3BC7220446174656E73636875747A20756E6420496E666F726D6174696F6E7366726569686569740D0A416E20646572205572616E696120342D31300D0A3130373837204265726C696E0D0A3033302F3133382038392D300D0A6D61696C626F7840646174656E73636875747A2D6265726C696E2E64650D0A687474703A2F2F7777772E646174656E73636875747A2D6265726C696E2E64650D0A416E737072656368706172746E65723A2044722E20416C6578616E64657220446978";
31 
32 	private Q_SLOTS:
initTestCase()33 		void initTestCase()
34 		{
35 			Env::getSingleton<LogHandler>()->init();
36 		}
37 
38 
invalidJson()39 		void invalidJson()
40 		{
41 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
42 
43 			QByteArray message("FooBar");
44 			const auto& obj = QJsonDocument::fromJson(message).object();
45 			QVERIFY(obj.isEmpty());
46 
47 			IfdEstablishPaceChannel msg(obj);
48 			QVERIFY(msg.isIncomplete());
49 
50 			QCOMPARE(logSpy.count(), 6);
51 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Missing value \"msg\"")));
52 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Invalid messageType received: \"\"")));
53 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Missing value \"ContextHandle\"")));
54 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Missing value \"SlotHandle\"")));
55 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Missing value \"InputData\"")));
56 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("The value of msg should be IFDEstablishPACEChannel")));
57 		}
58 
59 
values()60 		void values()
61 		{
62 			EstablishPaceChannel establishPaceChannel;
63 			const IfdEstablishPaceChannel ifdEstablishPaceChannel("SlotHandle", establishPaceChannel, 6);
64 
65 			QVERIFY(!ifdEstablishPaceChannel.isIncomplete());
66 			QCOMPARE(ifdEstablishPaceChannel.getType(), RemoteCardMessageType::IFDEstablishPACEChannel);
67 			QCOMPARE(ifdEstablishPaceChannel.getContextHandle(), QString());
68 			QCOMPARE(ifdEstablishPaceChannel.getSlotHandle(), QStringLiteral("SlotHandle"));
69 			QCOMPARE(ifdEstablishPaceChannel.getInputData(), establishPaceChannel);
70 			QCOMPARE(ifdEstablishPaceChannel.getPreferredPinLength(), 6);
71 		}
72 
73 
toJson_data()74 		void toJson_data()
75 		{
76 			QTest::addColumn<IfdVersion::Version>("version");
77 			QTest::addColumn<QByteArray>("inputApdu");
78 
79 			QTest::newRow("Unknown") << IfdVersion::Version::Unknown << QByteArray("ff9a0402073005a10302010300");
80 			QTest::newRow("v0") << IfdVersion::Version::v0 << QByteArray("ff9a0402073005a10302010300");
81 			QTest::newRow("v2") << IfdVersion::Version::v2 << QByteArray("0300000000");
82 		}
83 
84 
toJson()85 		void toJson()
86 		{
87 			QFETCH(IfdVersion::Version, version);
88 			QFETCH(QByteArray, inputApdu);
89 
90 			const IfdEstablishPaceChannel ifdEstablishPaceChannel(QStringLiteral("SlotHandle"), EstablishPaceChannel(PacePasswordId::PACE_PIN), 6);
91 
92 			const QByteArray& byteArray = ifdEstablishPaceChannel.toByteArray(version, QStringLiteral("TestContext"));
93 			QCOMPARE(byteArray,
94 					QByteArray("{\n"
95 							   "    \"ContextHandle\": \"TestContext\",\n"
96 							   "    \"InputData\": \"[DATA]\",\n"
97 							   "[LENGTH]"
98 							   "    \"SlotHandle\": \"SlotHandle\",\n"
99 							   "    \"msg\": \"IFDEstablishPACEChannel\"\n"
100 							   "}\n").replace("[DATA]", inputApdu).replace("[LENGTH]", version >= IfdVersion::Version::v2 ? QByteArray("    \"PreferredPinLength\": 6,\n") : QByteArray()));
101 
102 			const QJsonObject obj = QJsonDocument::fromJson(byteArray).object();
103 			QCOMPARE(obj.size(), version >= IfdVersion::Version::v2 ? 5 : 4);
104 			QCOMPARE(obj.value(QLatin1String("msg")).toString(), QStringLiteral("IFDEstablishPACEChannel"));
105 			QCOMPARE(obj.value(QLatin1String("ContextHandle")).toString(), QStringLiteral("TestContext"));
106 			QCOMPARE(obj.value(QLatin1String("SlotHandle")).toString(), QStringLiteral("SlotHandle"));
107 			QCOMPARE(obj.value(QLatin1String("InputData")).toString(), QString::fromLatin1(inputApdu));
108 			QCOMPARE(obj.value(QLatin1String("PreferredPinLength")).toInt(), version >= IfdVersion::Version::v2 ? 6 : 0);
109 		}
110 
111 
fromJson_data()112 		void fromJson_data()
113 		{
114 			QTest::addColumn<QByteArray>("inputData");
115 			QTest::addColumn<bool>("incomplete");
116 
117 			QByteArray ccidHex("FF9A04020002CE308202CAA103020103A3170415 CHAT A48202A8 CERT 01 00");
118 			ccidHex.replace(" CHAT ", mChatHex);
119 			ccidHex.replace(" CERT ", mCertHex);
120 			QTest::newRow("CCID") << ccidHex << !IfdVersion(IfdVersion::Version::v0).isSupported();
121 
122 			QByteArray pcscHex("0315 CHAT 00A802 CERT ");
123 			pcscHex.replace(" CHAT ", mChatHex);
124 			pcscHex.replace(" CERT ", mCertHex);
125 			QTest::newRow("PCSC") << pcscHex << false;
126 
127 			QTest::newRow("empty") << QByteArray() << true;
128 			QTest::newRow("shortGarbage") << QByteArray("1137") << true;
129 			QTest::newRow("longGarbage") << QByteArray("113711371137") << true;
130 		}
131 
132 
fromJson()133 		void fromJson()
134 		{
135 			QFETCH(QByteArray, inputData);
136 			QFETCH(bool, incomplete);
137 
138 			const bool v0Supported = IfdVersion(IfdVersion::Version::v0).isSupported();
139 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
140 
141 			QByteArray message(R"({
142 									"ContextHandle": "TestContext",
143 									"InputData": "[DATA]",
144 									"PreferredPinLength": 6,
145 									"SlotHandle": "SlotHandle",
146 									"msg": "IFDEstablishPACEChannel"
147 								 })");
148 			message.replace("[DATA]", inputData);
149 
150 			const QJsonObject& obj = QJsonDocument::fromJson(message).object();
151 			const IfdEstablishPaceChannel ifdEstablishPaceChannel(obj);
152 			QCOMPARE(ifdEstablishPaceChannel.isIncomplete(), incomplete);
153 			QCOMPARE(ifdEstablishPaceChannel.getType(), RemoteCardMessageType::IFDEstablishPACEChannel);
154 			QCOMPARE(ifdEstablishPaceChannel.getContextHandle(), QStringLiteral("TestContext"));
155 			QCOMPARE(ifdEstablishPaceChannel.getSlotHandle(), QStringLiteral("SlotHandle"));
156 			if (incomplete)
157 			{
158 				QCOMPARE(ifdEstablishPaceChannel.getInputData(), EstablishPaceChannel());
159 			}
160 			else
161 			{
162 				QCOMPARE(ifdEstablishPaceChannel.getInputData(), EstablishPaceChannel(PacePasswordId::PACE_PIN, QByteArray::fromHex(mChatHex), QByteArray::fromHex(mCertHex)));
163 			}
164 			QCOMPARE(ifdEstablishPaceChannel.getPreferredPinLength(), 6);
165 
166 			QCOMPARE(logSpy.count(), incomplete ? v0Supported ? 3 : 2 : 0);
167 			if (incomplete)
168 			{
169 				if (v0Supported)
170 				{
171 					QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("(v0) The value of InputData should be as defined in TR-03119 section D.3")));
172 					QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("(v2) The value of InputData should be as defined in PC/SC Part 10 AMD1 section 2.6.16")));
173 				}
174 				else
175 				{
176 					QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("The value of InputData should be as defined in PC/SC Part 10 AMD1 section 2.6.16")));
177 				}
178 			}
179 		}
180 
181 
msgField_data()182 		void msgField_data()
183 		{
184 			QTest::addColumn<RemoteCardMessageType>("type");
185 
186 			const auto& msgTypes = Enum<RemoteCardMessageType>::getList();
187 			for (const auto& type : msgTypes)
188 			{
189 				QTest::newRow(getEnumName(type).data()) << type;
190 			}
191 		}
192 
193 
msgField()194 		void msgField()
195 		{
196 			QFETCH(RemoteCardMessageType, type);
197 
198 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
199 
200 			QByteArray message(R"({
201 									"ContextHandle": "TestContext",
202 									"InputData": "0100000000",
203 									"PreferredPinLength": 6,
204 									"SlotHandle": "SlotHandle",
205 									"msg": "%1"
206 							   })");
207 			const QJsonObject& obj = QJsonDocument::fromJson(message.replace("%1", QTest::currentDataTag())).object();
208 			const IfdEstablishPaceChannel ifdEstablishPaceChannel(obj);
209 
210 			if (type == RemoteCardMessageType::IFDEstablishPACEChannel)
211 			{
212 				QVERIFY(!ifdEstablishPaceChannel.isIncomplete());
213 				QCOMPARE(ifdEstablishPaceChannel.getType(), RemoteCardMessageType::IFDEstablishPACEChannel);
214 
215 				QCOMPARE(logSpy.count(), 0);
216 
217 				return;
218 			}
219 
220 			QVERIFY(ifdEstablishPaceChannel.isIncomplete());
221 			QCOMPARE(ifdEstablishPaceChannel.getType(), type);
222 
223 			if (type == RemoteCardMessageType::UNDEFINED)
224 			{
225 				QCOMPARE(logSpy.count(), 2);
226 				QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Invalid messageType received: \"UNDEFINED\"")));
227 				QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("The value of msg should be IFDEstablishPACEChannel")));
228 
229 				return;
230 			}
231 
232 			QCOMPARE(logSpy.count(), 1);
233 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("The value of msg should be IFDEstablishPACEChannel")));
234 		}
235 
236 
wrongTypes()237 		void wrongTypes()
238 		{
239 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
240 
241 			const QByteArray message(R"({
242 										"ContextHandle": "TestContext",
243 										"InputData": 1,
244 										"PreferredPinLength": "Hello World!",
245 										"SlotHandle": 2,
246 										"msg": "IFDEstablishPACEChannel"
247 									 })");
248 
249 			const QJsonObject& obj = QJsonDocument::fromJson(message).object();
250 			const IfdEstablishPaceChannel ifdEstablishPaceChannel(obj);
251 			QVERIFY(ifdEstablishPaceChannel.isIncomplete());
252 			QCOMPARE(ifdEstablishPaceChannel.getType(), RemoteCardMessageType::IFDEstablishPACEChannel);
253 			QCOMPARE(ifdEstablishPaceChannel.getContextHandle(), QStringLiteral("TestContext"));
254 			QCOMPARE(ifdEstablishPaceChannel.getSlotHandle(), QString());
255 			QCOMPARE(ifdEstablishPaceChannel.getInputData(), EstablishPaceChannel());
256 			QCOMPARE(ifdEstablishPaceChannel.getPreferredPinLength(), 0);
257 
258 			QCOMPARE(logSpy.count(), 3);
259 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("The value of \"SlotHandle\" should be of type \"string\"")));
260 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("The value of \"InputData\" should be of type \"string\"")));
261 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("The value of \"PreferredPinLength\" should be of type \"number\"")));
262 		}
263 
264 
wrongInputData()265 		void wrongInputData()
266 		{
267 			const bool v0Supported = IfdVersion(IfdVersion::Version::v0).isSupported();
268 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
269 
270 			QByteArray message(R"({
271 									"ContextHandle": "TestContext",
272 									"InputData": "Hello World!",
273 									"PreferredPinLength": 6,
274 									"SlotHandle": "SlotHandle",
275 									"msg": "IFDEstablishPACEChannel"
276 							   })");
277 
278 			const QJsonObject& obj = QJsonDocument::fromJson(message).object();
279 			const IfdEstablishPaceChannel ifdEstablishPaceChannel(obj);
280 			QVERIFY(ifdEstablishPaceChannel.isIncomplete());
281 			QCOMPARE(ifdEstablishPaceChannel.getType(), RemoteCardMessageType::IFDEstablishPACEChannel);
282 			QCOMPARE(ifdEstablishPaceChannel.getContextHandle(), QStringLiteral("TestContext"));
283 			QCOMPARE(ifdEstablishPaceChannel.getSlotHandle(), QStringLiteral("SlotHandle"));
284 			QCOMPARE(ifdEstablishPaceChannel.getInputData(), EstablishPaceChannel());
285 
286 			QCOMPARE(logSpy.count(), v0Supported ? 3 : 2);
287 			if (v0Supported)
288 			{
289 				QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("(v0) The value of InputData should be as defined in TR-03119 section D.3")));
290 				QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("(v2) The value of InputData should be as defined in PC/SC Part 10 AMD1 section 2.6.16")));
291 			}
292 			else
293 			{
294 				QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("The value of InputData should be as defined in PC/SC Part 10 AMD1 section 2.6.16")));
295 			}
296 		}
297 
298 
preferredPinLength_data()299 		void preferredPinLength_data()
300 		{
301 			QTest::addColumn<QByteArray>("json");
302 			QTest::addColumn<int>("preferredPinLength");
303 
304 			QTest::newRow("empty") << QByteArray() << 0;
305 			QTest::newRow("0") << QByteArray(R"("PreferredPinLength": 0,)") << 0;
306 			QTest::newRow("5") << QByteArray(R"("PreferredPinLength": 5,)") << 5;
307 			QTest::newRow("6") << QByteArray(R"("PreferredPinLength": 6,)") << 6;
308 		}
309 
310 
preferredPinLength()311 		void preferredPinLength()
312 		{
313 			QFETCH(QByteArray, json);
314 			QFETCH(int, preferredPinLength);
315 
316 			QByteArray message(R"({
317 									"ContextHandle": "TestContext",
318 									"InputData": "0300000000",
319 									[JSON]
320 									"SlotHandle": "SlotHandle",
321 									"msg": "IFDEstablishPACEChannel"
322 								 })");
323 			message.replace("[JSON]", json);
324 
325 			const QJsonObject& obj = QJsonDocument::fromJson(message).object();
326 			const IfdEstablishPaceChannel ifdEstablishPaceChannel(obj);
327 			QVERIFY(!ifdEstablishPaceChannel.isIncomplete());
328 			QCOMPARE(ifdEstablishPaceChannel.getType(), RemoteCardMessageType::IFDEstablishPACEChannel);
329 			QCOMPARE(ifdEstablishPaceChannel.getContextHandle(), QStringLiteral("TestContext"));
330 			QCOMPARE(ifdEstablishPaceChannel.getSlotHandle(), QStringLiteral("SlotHandle"));
331 			QCOMPARE(ifdEstablishPaceChannel.getInputData(), EstablishPaceChannel(PacePasswordId::PACE_PIN));
332 			QCOMPARE(ifdEstablishPaceChannel.getPreferredPinLength(), preferredPinLength);
333 		}
334 
335 
336 };
337 
338 QTEST_GUILESS_MAIN(test_IfdEstablishPaceChannel)
339 #include "test_IfdEstablishPaceChannel.moc"
340