add working api code
This commit is contained in:
parent
5fb8af9d4f
commit
941a40a38c
282
app/src/main/cpp/api.cpp
Normal file
282
app/src/main/cpp/api.cpp
Normal file
@ -0,0 +1,282 @@
|
||||
//
|
||||
// Created by ACER on 2023/02/24.
|
||||
//
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <functional>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
|
||||
#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() {
|
||||
|
||||
}
|
229
app/src/main/cpp/api.h
Normal file
229
app/src/main/cpp/api.h
Normal file
@ -0,0 +1,229 @@
|
||||
//
|
||||
// Created by ACER on 2023/02/24.
|
||||
//
|
||||
|
||||
#ifndef CCCC_API_H
|
||||
#define CCCC_API_H
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
#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<bytes(bytes)> 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
|
Loading…
Reference in New Issue
Block a user