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