From 941a40a38cb64ae68fe9b8841fa7cabdf5f83c0e Mon Sep 17 00:00:00 2001 From: nganhkhoa Date: Wed, 1 Mar 2023 10:52:54 +0700 Subject: [PATCH] add working api code --- app/src/main/cpp/api.cpp | 282 +++++++++++++++++++++++++++++++++++++++ app/src/main/cpp/api.h | 229 +++++++++++++++++++++++++++++++ 2 files changed, 511 insertions(+) create mode 100644 app/src/main/cpp/api.cpp create mode 100644 app/src/main/cpp/api.h diff --git a/app/src/main/cpp/api.cpp b/app/src/main/cpp/api.cpp new file mode 100644 index 0000000..8a86bdb --- /dev/null +++ b/app/src/main/cpp/api.cpp @@ -0,0 +1,282 @@ +// +// Created by ACER on 2023/02/24. +// + +#include +#include +#include + +#include +#include + +#include "api.h" +#include "consts.h" +#include "utils.h" +#include "des.h" +#include "sha1.h" + +bytes Connector::finalizeAPDU(int cla, int ins, int p1, int p2, int ne, bytes& data) { + int s = data.size(); + bool extended = s > 255 || ne > 256; + bytes r = {uint8_t(cla), uint8_t(ins), uint8_t(p1), uint8_t(p2)}; + if (extended) { + r.push_back(0); + r.push_back(s >> 8); + } + if (!data.empty()) { + r.push_back(s); + r.insert(r.end(), data.begin(), data.end()); + } + if (ne == 0) + return r; + + ne = (ne == 256 || ne == 65536) ? 0 : ne; + if (extended) { + if (data.empty()) { + r.push_back(0); + } + r.push_back((ne >> 8)); + } + r.push_back(ne); + return r; +} + +Connector::Response Connector::sendICC(int cla, int ins, int p1, int p2, int ne, bytes& data) { + LOGI("send %02x%02x%02x%02x", cla, ins, p1, p2); + logBytes(" send data %s", data); + + if (sm) { + // wrap + cla |= ISO7816_CLA::SM_HEADER_AUTHN; + + bytes dataDO; + if (!data.empty()) { + iso9797_pad(data); + sm->encrypt(data); + if (ins == ISO7816_INS::READ_BINARY_EXT) { + dataDO = sm->do85(data); + } else { + dataDO = sm->do87(data, true); + } + } + + bytes do97 = sm->do97(ne); + + bytes M = {uint8_t(cla), uint8_t(ins), uint8_t(p1), uint8_t(p2)}; + iso9797_pad(M); + M.insert(M.end(), dataDO.begin(), dataDO.end()); + M.insert(M.end(), do97.begin(), do97.end()); + + bytes N = sm->next_ssc(); + N.insert(N.end(), M.begin(), M.end()); + iso9797_pad(N); + + bytes CC = sm->mac(N); + bytes do8e = sm->do8e(CC); + + data.clear(); + data.insert(data.end(), dataDO.begin(), dataDO.end()); + data.insert(data.end(), do97.begin(), do97.end()); + data.insert(data.end(), do8e.begin(), do8e.end()); + + ne = 256; + } + bytes command = finalizeAPDU(cla, ins, p1, p2, ne, data); + bytes raw_response = transceive(command); + logBytes("raw response %s", raw_response); + + auto response = decodeResponse(raw_response); + logBytes(" raw decoded %s", response.data); + LOGI(" status %02x%02x", response.code >> 8, response.code & 0x0f); + logBytes(" raw %s", raw_response); + + if (sm) { + // unwrap + + // data missing || data invalid + if (response.code >> 8 == 0x69 && ((response.code & 0x0f == 0x87) || (response.code & 0x0f == 0x88))) { + return response; + } + if (response.data.empty()) return response; + + auto do = sm->parseDO(response.data); + auto do99 = sm->parseDO99(response.data, do.size); + auto do8e = sm->parseDO8E(response.data, do99.size); + bytes K = sm->generateK(response.data, do8e); + uint64_t CC = bytes2num(sm->mac(K)); + + if (CC != bytes2num(do8e.value)) { + throw "Decoding message failure"; + } + response.data = sm->decrypt(do.value); + response.success = true; + response.code = do99; + + } + auto response = decodeResponse(raw_response); + logBytes(" decoded %s", response.data); + LOGI(" status %02x%02x", response.code >> 8, response.code & 0x0f); + logBytes(" raw %s", raw_response); + return response; +} + +Connector::Response Connector::decodeResponse(bytes& response) { + int s = response.size(); + Response r; + if (s < 2) { + r.success = false; + return r; + } + r.success = response[s - 2] == APDUsuccess; + r.code = response[s - 2] << 8 | response[s - 1]; + r.data.assign(response.begin(), response.end() - 2); + return r; +} + +Connector::Response Connector::selectFile(int p1, int p2, bytes& data) { + return sendICC(ISO7816_CLA::NO_SM, ISO7816_INS::SELECT_FILE, p1, p2, 0, data); +} + +Connector::Response Connector::selectFileByDFName(bytes&& fileId) { + return selectFile(ISO97816_SelectFileP1::byDFName, defaultSelectP2, fileId); +} + +Connector::Response Connector::getChallenge(int challengeLen) { + bytes t; + return sendICC(ISO7816_CLA::NO_SM, ISO7816_INS::GET_CHALLENGE, 0x00, 0x00, challengeLen, t); +} + +Connector::Response Connector::externalAuthenticate(bytes& eifd, uint64_t mifd) { + bytes data; + data.insert(data.end(), eifd.begin(), eifd.end()); + for (size_t i = 0; i < 8; i++) { + data.push_back(((uint8_t*)&mifd)[7 - i]); + } + return sendICC(ISO7816_CLA::NO_SM, ISO7816_INS::EXTERNAL_AUTHENTICATE, 0x00, 0x00, data.size(), data); +} + +Connector::Response Connector::readBinaryBySFI(int sfi, int offset) { + bytes t; + return sendICC(ISO7816_CLA::NO_SM, ISO7816_INS::READ_BINARY, sfi, offset, readAheadLength, t); +} + +Connector::Response Connector::readBinaryExt(int offset, int ne) { + // make data with TLV 0x54 for offset + bytes data; + int truncated_offset; + return sendICC(ISO7816_CLA::NO_SM, ISO7816_INS::READ_BINARY_EXT, 0, 0, truncated_offset, data); +} + +bool Connector::selectDf1() { + auto response = selectFileByDFName(bytes(Df1Name, Df1Name + sizeof(Df1Name))); + return response.success; +} + +void Connector::initBAC() { + // in bytes + int nonceLen = 8; + int kLen = 16; + int sLen = 2 * nonceLen + kLen; + int rLen = sLen; + int eLen = sLen; + int macLen = 8; + + // mrz.padRight(9, '<') + mrz_check_digit + // + birth yymmdd + birth_check_digit + // + expiry yymmdd + expiry_check_digit + const char dbaKeys[] = "098002079798112232311229"; + const int dbaKeysSize = 9 + 1 + 6 + 1 + 6 + 1; + bytes dbaKeysSeed(20); + SHA1((char*)dbaKeysSeed.data(), dbaKeys, dbaKeysSize); + dbaKeysSeed.resize(16); + + const bytes Kenc = deriveKeyDesEDE(dbaKeysSeed); + const bytes Kmac = deriveKeyISO9797(dbaKeysSeed); + + const uint64_t Kenc1 = bytes2num(bytes(Kenc.begin(), Kenc.begin() + 8)); + const uint64_t Kenc2 = bytes2num(bytes(Kenc.begin() + 8, Kenc.end())); + const uint64_t Kmac1 = bytes2num(bytes(Kmac.begin(), Kmac.begin() + 8)); + const uint64_t Kmac2 = bytes2num(bytes(Kmac.begin() + 8, Kmac.end())); + + logBytes("Kenc: %s", Kenc); + logBytes("Kmac: %s", Kmac); + + // assert success + const bytes RNDicc = getChallenge(nonceLen).data; + + const bytes RNDifd = randomBytes(nonceLen); + const bytes Kifd = randomBytes(kLen); + + bytes S; + S.insert(S.end(), RNDifd.begin(), RNDifd.end()); + S.insert(S.end(), RNDicc.begin(), RNDicc.end()); + S.insert(S.end(), Kifd.begin(), Kifd.end()); + + logBytes("RNDicc %s", RNDicc); + logBytes("RNDifd %s", RNDifd); + logBytes("Kifd %s", Kifd); + logBytes("S %s", S); + + bytes Eifd = tripledes_cbc_encrypt(S, Kenc1, Kenc2, Kenc1); + uint64_t Mifd = iso9797_mac(Eifd, Kmac1, Kmac2, Kmac1); + + logBytes("Eifd %s", Eifd); + LOGI("Mifd: %llx", Mifd); + + const auto pairEiccMicc = externalAuthenticate(Eifd, Mifd); + if (!pairEiccMicc.success) { + throw "External Authentication fail"; + } + const bytes Eicc = bytes(pairEiccMicc.data.begin(), pairEiccMicc.data.begin() + eLen); + const uint64_t Micc = bytes2num(bytes(pairEiccMicc.data.begin() + eLen, pairEiccMicc.data.end())); + + logBytes("Eicc %s", Eifd); + LOGI("Micc: %llx", Micc); + + uint64_t Micc_verify = iso9797_mac(Eicc, 0x58effeadb594fe7c, 0x8a43e9f8c2d0a408, 0x58effeadb594fe7c); + LOGI("Verify Mac: %llx", Micc_verify); + + if (Micc != Micc_verify) { + throw "Authentication fail"; + } + + const bytes R = tripledes_cbc_decrypt(Eicc, 0xfe43f1ab686eb334, 0xe68fea8cea31dfc7, 0xfe43f1ab686eb334); + const bytes eRNDifd = bytes(R.begin() + nonceLen, R.begin() + nonceLen * 2); + const bytes Kicc = bytes(R.begin() + nonceLen * 2, R.end()); + + logBytes("R: %s", R); + logBytes("eRNDifd: %s", eRNDifd); + logBytes("Kicc: %s", Kicc); + + if (bytes2num(eRNDifd) != bytes2num(RNDifd)) { + throw "Authentication fail"; + } + + // session keys + bytes keyseed; + for (size_t i = 0; i < Kifd.size(); i++) { + keyseed.push_back(Kifd[i] ^ Kicc[i]); + } + + bytes encryptKey = deriveKeyDesEDE(keyseed); + bytes macKey = deriveKeyISO9797(keyseed); + + size_t suffix = nonceLen / 2; + bytes ssc; + ssc.insert(ssc.end(), RNDicc.begin() + suffix, RNDicc.end()); + ssc.insert(ssc.end(), RNDifd.begin() + suffix, RNDifd.end()); + + uint64_t ssc_value = bytes2num(ssc); + + logBytes("encrypt key: %s", encryptKey); + logBytes("mac key: %s", macKey); + logBytes("ssc: %s", ssc); + LOGI("ssc %llx", ssc_value); + + sm = new SecureMessaging(encryptKey, macKey, ssc_value); + return; +} + +void Connector::readEFCOM() { + +} \ No newline at end of file diff --git a/app/src/main/cpp/api.h b/app/src/main/cpp/api.h new file mode 100644 index 0000000..1cbf315 --- /dev/null +++ b/app/src/main/cpp/api.h @@ -0,0 +1,229 @@ +// +// Created by ACER on 2023/02/24. +// + +#ifndef CCCC_API_H +#define CCCC_API_H + +#include +#include + +#include "utils.h" +#include "des.h" + + +class TLV { +public: + int tag; + int length; + bytes value; + int size; // full encoded length + + TLV(int tag, bytes data) : tag(tag), length(data.size()), value(data) { + auto tag_nbyte = bytecount(tag); + auto len_nbyte = bytecount(length); + // assume tag is 1 byte for now + size = tag_nbyte + len_nbyte + value.size() + (len_nbyte > 1 ? 1 : 0); + } + + // decode to TLV + TLV(bytes data) { + size_t offset = 0; + tag = data[offset++]; + if (tag == 0x1f) { + // multi byte tag + throw "Cannot decode multi-byte tag yet"; + } + + // decode length + length = data[offset++] & 0xff; + if ((length & 0x80) == 0x80) { + // multi byte length + size_t length_nbyte = length & 0x7f; + if (length_nbyte > 3) { + throw "length too big to decode"; + } + for (size_t i = 0; i < length_nbyte; i++) { + length <<= 8; + length |= data[offset++]; + } + } + + value.insert(value.end(), data.begin() + offset, data.begin() + offset + length); + size = offset + length; + } + + // encode to TLV + bytes encode() { + auto tag_nbyte = bytecount(tag); + auto tag_encoded = num2bytes(tag); + tag_encoded.erase(tag_encoded.begin(), tag_encoded.end() - tag_nbyte); + + auto len_nbyte = bytecount(length); + bytes len_encoded; + if (length < 0x80) { + len_encoded.push_back(length); + } else { + len_encoded.push_back(len_nbyte | 0x80); + auto x = num2bytes(length); + len_encoded.insert(len_encoded.end(), x.begin() + 8 - len_nbyte, x.end()); + } + + bytes encoded; + encoded.insert(encoded.end(), tag_encoded.begin(), tag_encoded.end()); + encoded.insert(encoded.end(), len_encoded.begin(), len_encoded.end()); + encoded.insert(value.end(), value.begin(), value.end()); + } +}; + +class SecureMessaging { + bytes encKey; + bytes macKey; + uint64_t ssc; + + // TLV encoding, simplified code + bytes buildDO(int tag, bytes data) { + // return TLV(tag, data).encode(); + if (tag > 255) { + // in this context only + throw "Tag value is overflow"; + } + if (data.empty()) return data; + + auto tag_nbyte = bytecount(tag); + auto tag_encoded = num2bytes(tag); + tag_encoded.erase(tag_encoded.begin(), tag_encoded.end() - tag_nbyte); + + size_t len = data.size(); + if (len > 0xffffff) { + throw "Length too big"; + } + auto len_nbyte = bytecount(len); + bytes len_encoded; + if (len < 0x80) { + len_encoded.push_back(len); + } else { + len_encoded.push_back(len_nbyte | 0x80); + auto x = num2bytes(len); + len_encoded.insert(len_encoded.end(), x.begin() + 8 - len_nbyte, x.end()); + } + + bytes encoded; + encoded.insert(encoded.end(), tag_encoded.begin(), tag_encoded.end()); + encoded.insert(encoded.end(), len_encoded.begin(), len_encoded.end()); + encoded.insert(encoded.end(), data.begin(), data.end()); + return encoded; + } +public: + + SecureMessaging(bytes encKey, bytes macKey, uint64_t ssc) : encKey(encKey), macKey(macKey), ssc(ssc) {} + + bytes do97(int ne) { + if (ne == 256 || ne == 65536) { + return buildDO(0x97, bytes(ne == 256 ? 1 : 2)); + } + // intToBin(ne, minLen=0) + return buildDO(0x97, bytes()); + } + bytes do85(bytes data) { + return buildDO(0x85, data); + } + bytes do87(bytes data, bool padded) { + if (data.empty()) return bytes(); + bytes d; + d.push_back(padded ? 0x1 : 0x2); + d.insert(d.end(), data.begin(), data.end()); + return buildDO(0x87, d); + } + bytes do8e(bytes data) { + return buildDO(0x8e, data); + } + + TLV* parseDO(bytes data) { + if (data.empty() || data[0] != 0x85 || data[0] != 0x87) { + return nullptr; + } + auto t = new TLV(data); + return t; + } + + TLV parseDO99(bytes data, size_t offset) { + if (data.empty()) { + return nullptr; + } + } + TLV parseDO8E(bytes data, size_t offset) { + + } + bytes generateK(bytes data, ) {} + + + bytes mac(bytes data) { + const uint64_t Kmac1 = bytes2num(bytes(macKey.begin(), macKey.begin() + 8)); + const uint64_t Kmac2 = bytes2num(bytes(macKey.begin() + 8, macKey.end())); + + uint64_t m = iso9797_mac(data, Kmac1, Kmac2, Kmac1); + return num2bytes(m); + } + + bytes encrypt(bytes& data) { + const uint64_t Kenc1 = bytes2num(bytes(encKey.begin(), encKey.begin() + 8)); + const uint64_t Kenc2 = bytes2num(bytes(encKey.begin() + 8, encKey.end())); + + bytes d = tripledes_cbc_encrypt(data, Kenc1, Kenc2, Kenc1); + data = d; + } + + bytes decrypt(bytes& data) { + const uint64_t Kenc1 = bytes2num(bytes(encKey.begin(), encKey.begin() + 8)); + const uint64_t Kenc2 = bytes2num(bytes(encKey.begin() + 8, encKey.end())); + + bytes d = tripledes_cbc_decrypt(data, Kenc1, Kenc2, Kenc1); + data = d; + } + + bytes next_ssc() { + ssc++; + return num2bytes(ssc); + } +}; + +class Connector { + typedef std::function transceive_type; + + struct Response { + bytes data; + bool success; + int code; + }; + +private: + bytes finalizeAPDU(int cla, int ins, int p1, int p2, int ne, bytes& data); + Connector::Response sendICC(int cla, int ins, int p1, int p2, int ne, bytes& data); + Connector::Response decodeResponse(bytes& response); + + Connector::Response selectFile(int p1, int p2, bytes& data); + Connector::Response selectFileByDFName(bytes&& fileId); + Connector::Response getChallenge(int challengeLen); + + Connector::Response externalAuthenticate(bytes& eifd, uint64_t mifd); + + Connector::Response readBinaryBySFI(int sfi, int offset); + Connector::Response readBinaryExt(int offset, int ne); + +// setupSecureMessaging(); + +public: + transceive_type transceive; + SecureMessaging* sm; + + int readAheadLength = 8; + + Connector(transceive_type transceive) : transceive(transceive) {} + + bool selectDf1(); + void initBAC(); + void readEFCOM(); +}; + +#endif //CCCC_API_H