eac20.js
Summary
Implementation of the Extended Access Control protocol in version 2.0
Class Summary
|
EAC20 |
Class implementing support for Extended Access Control V2
|
load("tools/eccutils.js");
load("pace.js");
load("chipauthentication.js");
load("restrictedidentification.js");
load("cvcertstore.js");
function EAC20(crypto, card) {
this.crypto = crypto;
this.card = card;
this.sm = null;
this.includeDPinAuthToken = false;
this.PACEInfos = new Array();
this.PACEDPs = new Array();
this.CAInfos = new Array();
this.CADPs = new Array();
this.CAPublicKeys = new Array();
this.RIInfos = new Array();
this.maxRData = 0;
this.maxCData = 239;
this.useFID = false;
this.verbose = true;
this.selectADFwithoutSM = false;
}
EAC20.ID_MRZ = 1;
EAC20.ID_CAN = 2;
EAC20.ID_PIN = 3;
EAC20.ID_PUK = 4;
EAC20.AID_LDS = new ByteString("A0000002471001", HEX);
EAC20.AID_eID = new ByteString("E80704007F00070302", HEX);
EAC20.AID_eSign = new ByteString("A000000167455349474E", HEX);
EAC20.SFI_CVCA = 0x1C;
EAC20.SFI_ChipSecurity = 0x1B;
EAC20.SFI_CardAccess = 0x1C;
EAC20.SFI_CardSecurity = 0x1D;
EAC20.SFI_COM = 0x1E;
EAC20.prototype.log = function(str) {
if (this.verbose) {
GPSystem.trace(str);
}
}
EAC20.prototype.processSecurityInfos = function(si, fromCardSecurity) {
this.log("SecurityInfos:");
this.log(si);
var id_PACE = new ByteString("id-PACE", OID);
var id_PACE_DH_GM = new ByteString("id-PACE-DH-GM", OID);
var id_PACE_ECDH_GM = new ByteString("id-PACE-ECDH-GM", OID);
var id_PACE_DH_IM = new ByteString("id-PACE-DH-IM", OID);
var id_PACE_ECDH_IM = new ByteString("id-PACE-ECDH-IM", OID);
var id_PK_ECDH = new ByteString("id-PK-ECDH", OID);
var id_CA = new ByteString("id-CA", OID);
var id_CA_DH = new ByteString("id-CA-DH", OID);
var id_CA_ECDH = new ByteString("id-CA-ECDH", OID);
var id_TA = new ByteString("id-TA", OID);
var id_PT = new ByteString("id-PT", OID);
for (var i = 0; i < si.elements; i++) {
var o = si.get(i);
assert((o.elements >= 1) && (o.elements <= 3));
var oid = o.get(0);
assert(oid.tag == ASN1.OBJECT_IDENTIFIER);
if (oid.value.startsWith(id_TA) == id_TA.length) {
this.log("TA : " + o);
} else if (oid.value.startsWith(id_PACE) == id_PACE.length) {
if (oid.value.equals(id_PACE_DH_GM) ||
oid.value.equals(id_PACE_ECDH_GM) ||
oid.value.equals(id_PACE_DH_IM) ||
oid.value.equals(id_PACE_ECDH_GM)) {
this.log("PaceDomainParameterInfo : " + o);
var pdpi = new PACEDomainParameterInfo(o);
this.log(pdpi);
var id = pdpi.parameterId;
if (typeof(id) == "undefined") {
id = 0;
}
if (!fromCardSecurity && (typeof(this.PACEDPs[id]) != "undefined")) {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Duplicate parameterId " + id + " for PACEDomainParameter");
}
this.PACEDPs[id] = pdpi;
} else {
this.log("PaceInfo : " + o);
var pi = new PACEInfo(o);
this.log(pi);
var id = pi.parameterId;
if (typeof(id) == "undefined") {
id = 0;
}
if (pi.version == 1) {
if (!fromCardSecurity && (typeof(this.PACEInfos[id]) != "undefined")) {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Duplicate parameterId " + id + " for PACEInfo");
}
} else {
id = 0;
}
this.PACEInfos[id] = pi;
}
} else if (oid.value.equals(id_PK_ECDH)) {
this.log("ChipAuthenticationPublicKeyInfo : " + o);
var capki = new ChipAuthenticationPublicKeyInfo(o);
this.log(capki);
var id = capki.keyId;
if (typeof(id) == "undefined") {
this.log("Using default key id 0");
id = 0;
}
if (!fromCardSecurity && (typeof(this.CAPublicKeys[id]) != "undefined")) {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Duplicate keyId " + id + " for ChipAuthenticationPublicKeyInfo");
}
this.CAPublicKeys[id] = capki;
} else if (oid.value.startsWith(id_CA) == id_CA.length) {
if (oid.value.equals(id_CA_DH) ||
oid.value.equals(id_CA_ECDH)) {
this.log("ChipAuthenticationDomainParameterInfo : " + o);
var cadpi = new ChipAuthenticationDomainParameterInfo(o);
this.log(cadpi);
var id = cadpi.keyId;
if (typeof(id) == "undefined") {
this.log("Using default key id 0");
id = 0;
}
if (!fromCardSecurity && (typeof(this.CADPs[id]) != "undefined")) {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Duplicate keyId " + id + " for ChipAuthenticationDomainParameter");
}
this.CADPs[id] = cadpi;
} else {
this.log("ChipAuthenticationInfo : " + o);
var cai = new ChipAuthenticationInfo(o);
this.log(cai);
var id = cai.keyId;
if (typeof(id) == "undefined") {
this.log("Using default key id 0");
id = 0;
}
if (!fromCardSecurity && (typeof(this.CAInfos[id]) != "undefined")) {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Duplicate keyId " + id + " for ChipAuthenticationInfo");
}
this.CAInfos[id] = cai;
}
} else if (oid.value.startsWith(RestrictedIdentification.id_RI) == RestrictedIdentification.id_RI.length) {
if (oid.value.equals(RestrictedIdentification.id_RI_DH) ||
oid.value.equals(RestrictedIdentification.id_RI_ECDH)) {
this.log("RestrictedIdentificationDomainParameterInfo : " + o);
var ridpi = new RestrictedIdentificationDomainParameterInfo(o);
this.log(ridpi);
if (!fromCardSecurity && (typeof(this.RIDP) != "undefined")) {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Duplicate RestrictedIdentificationDomainParameter");
this.RIDP = ridpi;
}
} else {
this.log("RestrictedIdentificationInfo : " + o);
var rii = new RestrictedIdentificationInfo(o);
this.log(rii);
var id = rii.keyId;
if (typeof(id) == "undefined") {
this.log("Using default key id 0");
id = 0;
}
if (!fromCardSecurity && (typeof(this.RIInfos[id]) != "undefined")) {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Duplicate keyId " + id + " for RestrictedIdentificationInfo");
}
this.RIInfos[id] = rii;
}
} else if (oid.value.equals(id_PT)) {
this.log("PrivilegedTerminalInfo : " + o);
this.processSecurityInfos(o.get(1), fromCardSecurity);
}
}
}
EAC20.prototype.readEFwithFID = function(fid) {
assert(fid.length == 2, "Length of fid must be 2 bytes");
this.card.sendSecMsgApdu(Card.ALL, 0x00, 0xA4, 0x02, 0x0C, fid, [0x9000]);
var bb = new ByteBuffer();
var offset = 0;
do {
var rsp = this.card.sendSecMsgApdu(Card.ALL, 0x00, 0xB0, offset >> 8, offset & 0xFF, this.maxRData);
bb.append(rsp);
offset += rsp.length;
} while ((this.card.SW == 0x9000) && (rsp.length > 0));
return bb.toByteString();
}
EAC20.prototype.updateEFwithFID = function(fid, data) {
assert(fid.length == 2, "Length of fid must be 2 bytes");
this.card.sendSecMsgApdu(Card.ALL, 0x00, 0xA4, 0x02, 0x0C, fid, [0x9000]);
var offset = 0;
while (offset < data.length) {
var len = data.length - offset;
len = this.maxCData < len ? this.maxCData : len;
this.card.sendSecMsgApdu(Card.ALL, 0x00, 0xD6, offset >> 8, offset & 0xFF, data.bytes(offset, len), [0x9000]);
offset += len;
}
}
EAC20.prototype.readEFwithSFI = function(sfi) {
assert(typeof(sfi) == "number", "Parameter must be a number");
if (this.useFID) {
var fid = ByteString.valueOf(0x0100 + sfi, 2);
return this.readEFwithFID(fid);
}
var rsp = this.card.sendSecMsgApdu(Card.ALL, 0x00, 0xB0, 0x80 | sfi, 0x00, this.maxRData, [0x9000]);
var bb = new ByteBuffer(rsp);
var offset = bb.length;
do {
var rsp = this.card.sendSecMsgApdu(Card.ALL, 0x00, 0xB0, offset >> 8, offset & 0xFF, this.maxRData);
bb.append(rsp);
offset += rsp.length;
} while ((this.card.SW == 0x9000) && (rsp.length > 0));
return bb.toByteString();
}
EAC20.prototype.updateEFwithSFI = function(sfi, data) {
assert(typeof(sfi) == "number", "Parameter must be a number");
if (this.useFID) {
var fid = ByteString.valueOf(0x0100 + sfi, 2);
return this.updateEFwithFID(fid, data);
}
var offset = 0;
var p1 = 0x80 | sfi;
while (offset < data.length) {
var len = data.length - offset;
len = this.maxCData < len ? this.maxCData : len;
this.card.sendSecMsgApdu(Card.ALL, 0x00, 0xD6, p1, offset & 0xFF, data.bytes(offset, len), [0x9000]);
offset += len;
p1 = offset >> 8;
}
}
EAC20.prototype.selectADF = function(aid) {
if (this.selectADFwithoutSM) {
this.card.sendApdu(0x00, 0xA4, 0x04, 0x0C, aid, [0x9000]);
} else {
this.card.sendSecMsgApdu(Card.ALL, 0x00, 0xA4, 0x04, 0x0C, aid, [0x9000]);
}
}
EAC20.prototype.selectLDS = function() {
this.selectADF(EAC20.AID_LDS);
}
EAC20.prototype.select_eID = function() {
this.selectADF(EAC20.AID_eID);
}
EAC20.prototype.select_eSign = function() {
this.selectADF(EAC20.AID_eSign);
}
EAC20.prototype.readDG14 = function() {
var cibin = this.readEFwithSFI(14);
var citlv = new ASN1(cibin);
this.log(citlv);
this.processSecurityInfos(citlv.get(0), true);
}
EAC20.prototype.readCVCA = function() {
var cvcabin = this.readEFwithSFI(EAC20.SFI_CVCA);
assert(cvcabin.byteAt(0) == 0x42);
var cvca = new ASN1(cvcabin);
this.lastCAR = new PublicKeyReference(cvca.value);
if (cvcabin.byteAt(cvca.size) == 0x42) {
var cvca = new ASN1(cvcabin.bytes(cvca.size));
this.previousCAR = new PublicKeyReference(cvca.value);
}
}
EAC20.prototype.readCardAccess = function() {
var cibin = this.readEFwithSFI(EAC20.SFI_CardAccess);
var citlv = new ASN1(cibin);
this.log(citlv);
this.processSecurityInfos(citlv, false);
}
EAC20.prototype.readCardInfo = EAC20.prototype.readCardAccess;
EAC20.prototype.readCardSecurity = function() {
var csbin = this.readEFwithSFI(EAC20.SFI_CardSecurity);
var cstlv = new ASN1(csbin);
this.log("EF.CardSecurity:");
this.log(cstlv);
var cms = new CMSSignedData(csbin);
var certs = cms.getSignedDataCertificates();
this.log("EF.CardSecurity Certificates:");
for (var i = 0; i < certs.length; i++) {
this.log(certs[i]);
}
this.log("DocSigner Signature is " + (cms.isSignerInfoSignatureValid(0) ? "valid" : "not valid"));
var data = cms.getSignedContent();
this.log(data);
var cstlv = new ASN1(data);
this.log(cstlv);
this.processSecurityInfos(cstlv, true);
}
EAC20.prototype.readChipSecurity = function() {
var csbin = this.readEFwithSFI(EAC20.SFI_ChipSecurity);
var cstlv = new ASN1(csbin);
this.log("EF.ChipSecurity:");
this.log(cstlv);
var cms = new CMSSignedData(csbin);
var certs = cms.getSignedDataCertificates();
this.log("EF.ChipSecurity Certificates:");
for (var i = 0; i < certs.length; i++) {
this.log(certs[i]);
}
this.log("DocSigner Signature is " + (cms.isSignerInfoSignatureValid(0) ? "valid" : "not valid"));
var data = cms.getSignedContent();
this.log(data);
var cstlv = new ASN1(data);
this.log(cstlv);
this.processSecurityInfos(cstlv, true);
}
EAC20.prototype.getPACEInfos = function() {
return this.PACEInfos;
}
EAC20.prototype.getPACEDomainParameterInfos = function() {
return this.PACEDPs;
}
EAC20.prototype.getCAInfos = function() {
return this.CAInfos;
}
EAC20.prototype.getCADomainParameterInfos = function() {
return this.CADPs;
}
EAC20.prototype.getCAKeyId = function(privileged) {
for (var i in this.CAInfos) {
if (this.CAInfos[i].keyId) {
if (privileged) {
privileged = false;
} else {
return this.CAInfos[i].keyId;
}
}
}
return 0;
}
EAC20.prototype.getRIKeyId = function(authOnly) {
for each (var rii in this.RIInfos) {
if (!authOnly == !rii.authorizedOnly) {
return rii.keyId;
}
}
return 0;
}
EAC20.decodeDocumentNumber = function(mrz) {
if (mrz.length == 88) {
var docno = mrz.substr(44, 10);
} else {
if (mrz.charAt(14) == "<") {
var sep = mrz.indexOf("<", 15);
var docno = mrz.substr(5,9).concat(mrz.substring(15,sep));
} else {
var docno = mrz.substr(5, 10);
}
}
return docno;
}
EAC20.prototype.hashMRZ = function(mrz) {
var hash_input = new ByteString(EAC20.decodeDocumentNumber(mrz), ASCII);
var strbin = new ByteString(mrz, ASCII);
if (strbin.length == 88) {
hash_input = hash_input.concat(strbin.bytes(57, 7));
hash_input = hash_input.concat(strbin.bytes(65, 7));
} else if (strbin.length == 90) {
hash_input = hash_input.concat(strbin.bytes(30, 7));
hash_input = hash_input.concat(strbin.bytes(38, 7));
} else {
throw new GPError("EAC20", GPError.INVALID_DATA, strbin.length, "MRZ must be either 88 or 90 character long");
}
this.log("Hash Input : " + hash_input.toString(ASCII));
var mrz_hash = this.crypto.digest(Crypto.SHA_1, hash_input);
this.log("MRZ Hash : " + mrz_hash);
return mrz_hash;
}
EAC20.prototype.calculateBACKey = function(mrz, keyno) {
var mrz_hash = this.hashMRZ(mrz);
var bb = new ByteBuffer(mrz_hash.bytes(0, 16));
bb.append(new ByteString("000000", HEX));
bb.append(keyno);
var keyval = this.crypto.digest(Crypto.SHA_1, bb.toByteString());
keyval = keyval.bytes(0, 16);
this.log("Value of Key : " + keyval);
var key = new Key();
key.setComponent(Key.DES, keyval);
return key;
}
EAC20.prototype.performBACWithMRZ = function(mrz) {
this.setIDPICC(new ByteString(EAC20.decodeDocumentNumber(mrz), ASCII));
var kenc = this.calculateBACKey(mrz, 1);
var kmac = this.calculateBACKey(mrz, 2);
this.performBAC(kenc, kmac);
}
EAC20.prototype.performBAC = function(kenc, kmac) {
var rndicc = this.card.sendApdu(0x00, 0x84, 0x00, 0x00, 0x08, [0x9000]);
var rndifd = this.crypto.generateRandom(8);
var kifd = this.crypto.generateRandom(16);
var plain = rndifd.concat(rndicc).concat(kifd);
var cryptogram = this.crypto.encrypt(kenc, Crypto.DES_CBC, plain, new ByteString("0000000000000000", HEX));
var mac = this.crypto.sign(kmac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2));
var autresp = this.card.sendApdu(0x00, 0x82, 0x00, 0x00, cryptogram.concat(mac), 0);
if (this.card.SW != 0x9000) {
this.log("Mutual authenticate failed with " + this.card.SW.toString(16) + " \"" + this.card.SWMSG + "\". MRZ correct ?");
throw new GPError("EAC20", GPError.CRYPTO_FAILED, 0, "Card did not accept MAC in BAC establishment");
}
cryptogram = autresp.bytes(0, 32);
mac = autresp.bytes(32, 8);
if (!this.crypto.verify(kmac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2), mac)) {
throw new GPError("EAC20", GPError.CRYPTO_FAILED, 0, "Card MAC did not verify correctly");
}
plain = this.crypto.decrypt(kenc, Crypto.DES_CBC, cryptogram, new ByteString("0000000000000000", HEX));
if (!plain.bytes(0, 8).equals(rndicc)) {
throw new GPError("EAC20", GPError.CRYPTO_FAILED, 0, "Card response does not contain matching RND.ICC");
}
if (!plain.bytes(8, 8).equals(rndifd)) {
throw new GPError("EAC20", GPError.CRYPTO_FAILED, 0, "Card response does not contain matching RND.IFD");
}
var kicc = plain.bytes(16, 16);
keyinp = kicc.xor(kifd);
var hashin = keyinp.concat(new ByteString("00000001", HEX));
var kencval = this.crypto.digest(Crypto.SHA_1, hashin);
kencval = kencval.bytes(0, 16);
var kenc = new Key();
kenc.setComponent(Key.DES, kencval);
var hashin = keyinp.concat(new ByteString("00000002", HEX));
var kmacval = this.crypto.digest(Crypto.SHA_1, hashin);
kmacval = kmacval.bytes(0, 16);
var kmac = new Key();
kmac.setComponent(Key.DES, kmacval);
var ssc = rndicc.bytes(4, 4).concat(rndifd.bytes(4, 4));
this.sm = new IsoSecureChannel(this.crypto);
this.sm.setEncKey(kenc);
this.sm.setMacKey(kmac);
this.sm.setSendSequenceCounter(ssc);
this.card.setCredential(this.sm);
}
EAC20.prototype.performPACE = function(parameterId, pwdid, pwd, chat) {
var paceinfo = this.PACEInfos[parameterId];
if (typeof(paceinfo) == "undefined") {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Unknown parameterId " + parameterId + " for PACEInfo");
}
var domainParameter;
this.includeDPinAuthToken = !(paceinfo.version >= 2);
if ((paceinfo.version == 1) || ((paceinfo.version == 2) && (parameterId > 31))) {
var pacedp = this.PACEDPs[parameterId];
if (typeof(pacedp) == "undefined") {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Unknown parameterId " + parameterId + " for PACEDomainParameterInfo");
}
domainParameter = pacedp.domainParameter;
} else {
domainParameter = PACEDomainParameterInfo.getStandardizedDomainParameter(parameterId);
}
if (!(pwd instanceof ByteString)) {
throw new GPError("EAC20", GPError.INVALID_TYPE, 0, "Argument pwd must be of type ByteString");
}
if ((chat != null) && !(chat instanceof ASN1)) {
throw new GPError("EAC20", GPError.INVALID_TYPE, 0, "Argument chat must be of type ASN1");
}
var pace = new PACE(this.crypto, paceinfo.protocol, domainParameter, paceinfo.version);
pace.setPassword(pwd);
var crt = new ByteBuffer();
crt.append((new ASN1(0x80, paceinfo.protocol)).getBytes());
crt.append(new ByteString("8301", HEX));
crt.append(pwdid);
if (chat != null) {
crt.append(chat.getBytes());
}
this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO, 0x00, 0x22, 0xC1, 0xA4, crt.toByteString(), [0x9000, 0x63C2, 0x63C1 ]);
var dado = new ASN1(0x7C);
dadobin = this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO|Card.RENC, 0x10, 0x86, 0x00, 0x00, dado.getBytes(), 0, [0x9000]);
var dado = new ASN1(dadobin);
assert(dado.tag == 0x7C);
assert(dado.elements == 1);
var encryptedNonceDO = dado.get(0);
assert(encryptedNonceDO.tag == 0x80);
var encryptedNonce = encryptedNonceDO.value;
this.log("Encrypted nonce: " + encryptedNonce);
pace.decryptNonce(encryptedNonce);
var mappingData = pace.getMappingData();
var dado = new ASN1(0x7C, new ASN1(0x81, mappingData));
dadobin = this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO|Card.RENC, 0x10, 0x86, 0x00, 0x00, dado.getBytes(), 0, [0x9000]);
var dado = new ASN1(dadobin);
assert(dado.tag == 0x7C);
assert(dado.elements == 1);
var mappingDataDO = dado.get(0);
assert(mappingDataDO.tag == 0x82);
pace.performMapping(mappingDataDO.value);
var ephemeralPublicKeyIfd = pace.getEphemeralPublicKey();
var dado = new ASN1(0x7C, new ASN1(0x83, ephemeralPublicKeyIfd));
dadobin = this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO|Card.RENC, 0x10, 0x86, 0x00, 0x00, dado.getBytes(), 0, [0x9000]);
var dado = new ASN1(dadobin);
assert(dado.tag == 0x7C);
assert(dado.elements == 1);
var ephemeralPublicKeyICC = dado.get(0);
assert(ephemeralPublicKeyICC.tag == 0x84);
this.idPICC = ephemeralPublicKeyICC.value.bytes(1, (ephemeralPublicKeyICC.value.length - 1) >> 1);
this.log("ID_PICC : " + this.idPICC);
pace.performKeyAgreement(ephemeralPublicKeyICC.value);
var authToken = pace.calculateAuthenticationToken();
var dado = new ASN1(0x7C, new ASN1(0x85, authToken));
dadobin = this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO|Card.RENC, 0x00, 0x86, 0x00, 0x00, dado.getBytes(), 0, [0x9000, 0x63C2, 0x63C1, 0x63C0, 0x6283 ]);
if ((this.card.SW & 0xFFF0) == 0x63C0) {
throw new GPError("EAC20", GPError.DEVICE_ERROR, this.card.SW, "Authentication failed: " + (this.card.SW & 0xF) + " tries left");
}
if (this.card.SW == 0x6300) {
throw new GPError("EAC20", GPError.DEVICE_ERROR, this.card.SW, "Authentication failed");
}
var dado = new ASN1(dadobin);
this.log(dado);
assert(dado.tag == 0x7C);
assert(dado.elements >= 1);
assert(dado.elements <= 3);
var authTokenDO = dado.get(0);
assert(authTokenDO.tag == 0x86);
if (dado.elements > 1) {
var cardo = dado.get(1);
assert(cardo.tag == 0x87);
this.lastCAR = new PublicKeyReference(cardo.value);
}
if (dado.elements > 2) {
var cardo = dado.get(2);
assert(cardo.tag == 0x88);
this.previousCAR = new PublicKeyReference(cardo.value);
}
var sm = null;
if (pace.verifyAuthenticationToken(authTokenDO.value)) {
this.log("Authentication token valid");
var symalgo = pace.getSymmetricAlgorithm();
if (symalgo == Key.AES) {
sm = new IsoSecureChannel(this.crypto, IsoSecureChannel.SSC_SYNC_ENC_POLICY);
sm.setEncKey(pace.kenc);
sm.setMacKey(pace.kmac);
sm.setMACSendSequenceCounter(new ByteString("00000000000000000000000000000000", HEX));
} else {
sm = new IsoSecureChannel(this.crypto);
sm.setEncKey(pace.kenc);
sm.setMacKey(pace.kmac);
sm.setMACSendSequenceCounter(new ByteString("0000000000000000", HEX));
}
this.card.setCredential(sm);
}
this.sm = sm;
}
EAC20.prototype.getTrustAnchorCAR = function(previous) {
if (previous) {
return this.previousCAR;
} else {
return this.lastCAR;
}
}
EAC20.prototype.verifyCertificateChain = function(cvcchain) {
for (var i = 0; i < cvcchain.length; i++) {
var cvc = cvcchain[i];
var car = cvc.getCAR().getBytes();
var pukrefdo = new ASN1(0x83, car);
var pukref = pukrefdo.getBytes();
this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO, 0x00, 0x22, 0x81, 0xB6, pukref, [0x9000]);
var tl = new TLVList(cvc.getBytes(), TLV.EMV);
var t = tl.index(0);
var v = t.getValue();
this.log("Certificate: ");
this.log(new ASN1(v));
this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO, 0x00, 0x2A, 0x00, 0xBE, v, [0x9000]);
}
this.terminalCert = cvcchain[cvcchain.length - 1];
}
EAC20.prototype.setIDPICC = function(idPICC) {
this.idPICC = idPICC;
}
EAC20.prototype.performTerminalAuthentication = function(termkey, auxdata, crypto) {
var signatureInput = this.performTerminalAuthenticationSetup(auxdata);
if (crypto == undefined) {
var crypto = this.crypto;
}
var signature = crypto.sign(termkey, CVC.getSignatureMech(this.terminalCert.getPublicKeyOID()), signatureInput);
var keysize = termkey.getSize();
if (keysize < 0) {
keysize = termkey.getComponent(Key.ECC_P).length;
} else {
keysize >>= 3;
}
signature = ECCUtils.unwrapSignature(signature, keysize);
this.log("Signature (Encoded):");
this.log(signature);
this.performTerminalAuthenticationFinal(signature);
}
EAC20.prototype.performTerminalAuthenticationSetup = function(auxdata) {
var idIFD = this.ca.getCompressedPublicKey();
var bb = new ByteBuffer();
if (typeof(this.cakeyId) != "undefined") {
bb.append(new ASN1(0x80, this.terminalCert.getPublicKeyOID()).getBytes());
}
bb.append(new ASN1(0x83, this.terminalCert.getCHR().getBytes()).getBytes());
if (typeof(this.cakeyId) != "undefined") {
if (auxdata) {
bb.append(auxdata);
}
bb.append(new ASN1(0x91, idIFD).getBytes());
}
var msedata = bb.toByteString();
this.log("Manage SE data:");
this.log(msedata);
this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO, 0x00, 0x22, 0x81, 0xA4, msedata, [0x9000]);
var challenge = this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO|Card.RENC, 0x00, 0x84, 0x00, 0x00, 8, [0x9000]);
var bb = new ByteBuffer();
bb.append(this.idPICC);
bb.append(challenge);
bb.append(idIFD);
if (auxdata) {
bb.append(auxdata);
}
var signatureInput = bb.toByteString();
this.log("Signature Input:");
this.log(signatureInput);
return signatureInput;
}
EAC20.prototype.performTerminalAuthenticationFinal = function(signature) {
this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO, 0x00, 0x82, 0x00, 0x00, signature, [0x9000]);
}
EAC20.prototype.performChipAuthenticationV1 = function(keyid) {
this.log("performChipAuthenticationV1() called");
if (typeof(keyid) == "undefined") {
keyid = 0;
}
var cainfo = this.CAInfos[keyid];
if (typeof(cainfo) == "undefined") {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Unknown keyId " + keyid + " for ChipAuthenticationInfo");
}
var capuk = this.CAPublicKeys[keyid];
if (typeof(capuk) == "undefined") {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Unknown keyId " + keyid + " for ChipAuthenticationPublicKeyInfo");
}
var domainParameter = capuk.domainParameter;
this.ca = new ChipAuthentication(this.crypto, cainfo.protocol, domainParameter);
this.ca.generateEphemeralCAKeyPair();
var bb = new ByteBuffer();
bb.append(new ASN1(0x91, this.ca.getEphemeralPublicKey()).getBytes());
if (typeof(cainfo.keyId) != "undefined") {
bb.append(new ByteString("8401", HEX));
bb.append(cainfo.keyId);
}
var msedata = bb.toByteString();
this.log("Manage SE data:");
this.log(msedata);
this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO, 0x00, 0x22, 0x41, 0xA6, msedata, [0x9000]);
this.ca.performKeyAgreement(capuk.publicKey);
this.log("Create DES based secure channel");
var sm = new IsoSecureChannel(this.crypto);
sm.setEncKey(this.ca.kenc);
sm.setMacKey(this.ca.kmac);
sm.setSendSequenceCounter(new ByteString("0000000000000000", HEX));
this.card.setCredential(sm);
this.sm = sm;
}
EAC20.prototype.prepareChipAuthentication = function(keyId) {
this.log("prepareChipAuthentication() called");
this.cakeyId = keyId;
var cainfo = this.CAInfos[keyId];
if (typeof(cainfo) == "undefined") {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Unknown keyId " + keyId + " for ChipAuthenticationInfo");
}
var cadp = this.CADPs[keyId];
if (typeof(cadp) == "undefined") {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Unknown keyId " + keyId + " for ChipAuthenticationDomainParameterInfo");
}
this.cadp = cadp;
this.ca = new ChipAuthentication(this.crypto, cainfo.protocol, cadp.domainParameter);
this.ca.includeDPinAuthToken = this.includeDPinAuthToken;
this.ca.generateEphemeralCAKeyPair();
}
EAC20.prototype.performChipAuthenticationV2 = function() {
this.log("performChipAuthenticationV2() called");
var cainfo = this.CAInfos[this.cakeyId];
if (typeof(cainfo) == "undefined") {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Unknown keyId " + this.cakeyId + " for ChipAuthenticationInfo");
}
if (this.ca.algo != cainfo.protocol) {
this.log("Special handling for ChipAuthenticationInfo in EF.CardSecurity overwriting ChipAuthenticationInfo in EF.CardAccess");
this.log("Protocol in EF.CardAccess: " + this.ca.algo);
this.log("Protocol is EF.CardSecurity: " + cainfo.protocol);
this.ca.algo = cainfo.protocol;
}
var capuk = this.CAPublicKeys[this.cakeyId];
if (typeof(capuk) == "undefined") {
throw new GPError("EAC20", GPError.INVALID_DATA, 0, "Unknown keyId " + this.cakeyId + " for ChipAuthenticationPublicKeyInfo");
}
var bb = new ByteBuffer();
bb.append(new ASN1(0x80, cainfo.protocol).getBytes());
if (typeof(cainfo.keyId) != "undefined") {
bb.append(new ByteString("8401", HEX));
bb.append(cainfo.keyId);
}
var msedata = bb.toByteString();
this.log("Manage SE data:");
this.log(msedata);
this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO, 0x00, 0x22, 0x41, 0xA4, msedata, [0x9000]);
var ephemeralPublicKeyIfd = this.ca.getEphemeralPublicKey();
var dado = new ASN1(0x7C, new ASN1(0x80, ephemeralPublicKeyIfd));
var dadobin = this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO|Card.RENC, 0x00, 0x86, 0x00, 0x00, dado.getBytes(), 0, [0x9000]);
this.log(dadobin);
var dado = new ASN1(dadobin);
assert(dado.tag == 0x7C);
assert(dado.elements == 2);
var nonceDO = dado.get(0);
assert(nonceDO.tag == 0x81);
var nonce = nonceDO.value;
var authTokenDO = dado.get(1);
assert(authTokenDO.tag == 0x82);
var authToken = authTokenDO.value;
this.ca.performKeyAgreement(capuk.publicKey, nonce);
var result = this.ca.verifyAuthenticationToken(authToken);
if (result) {
this.log("Authentication token valid");
if (this.ca.algo.equals(ChipAuthentication.id_CA_ECDH_3DES_CBC_CBC)) {
this.log("Create DES based secure channel");
var sm = new IsoSecureChannel(this.crypto);
sm.setEncKey(this.ca.kenc);
sm.setMacKey(this.ca.kmac);
sm.setMACSendSequenceCounter(new ByteString("0000000000000000", HEX));
} else {
this.log("Create AES based secure channel");
var sm = new IsoSecureChannel(this.crypto, IsoSecureChannel.SSC_SYNC_ENC_POLICY);
sm.setEncKey(this.ca.kenc);
sm.setMacKey(this.ca.kmac);
sm.setMACSendSequenceCounter(new ByteString("00000000000000000000000000000000", HEX));
}
this.card.setCredential(sm);
this.sm = sm;
} else {
this.log("Authentication token invalid");
}
return result;
}
EAC20.prototype.verifyAuxiliaryData = function(oid) {
var o = new ASN1(ASN1.OBJECT_IDENTIFIER, oid);
this.card.sendSecMsgApdu(Card.ALL, 0x80, 0x20, 0x80, 0x00, o.getBytes(), [0x9000,0x6300]);
return this.card.SW == 0x9000;
}
EAC20.prototype.performChipAuthentication = function(keyid) {
if (typeof(this.cakeyId) != "undefined") {
return this.performChipAuthenticationV2();
} else {
return this.performChipAuthenticationV1(keyid);
}
}
EAC20.prototype.performRestrictedIdentification = function(keyId, sectorPublicKey, sectorPublicKeyIndex) {
var bb = new ByteBuffer();
bb.append(new ASN1(0x80, new ByteString("id-RI-ECDH-SHA-256", OID)).getBytes());
bb.append(new ByteString("8401", HEX));
bb.append(keyId);
var msedata = bb.toByteString();
this.log("Manage SE data:");
this.log(msedata);
this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO, 0x00, 0x22, 0x41, 0xA4, msedata, [0x9000]);
var tag = 0xA0;
if (sectorPublicKeyIndex) {
tag = 0xA0 + (sectorPublicKeyIndex << 1);
}
var dado = new ASN1(0x7C, new ASN1(tag, sectorPublicKey.bytes(5)));
this.log("GA Input: " + dado.getBytes());
var dadobin = this.card.sendSecMsgApdu(Card.CPRO|Card.CENC|Card.RPRO|Card.RENC, 0x00, 0x86, 0x00, 0x00, dado.getBytes(), 0, [0x9000]);
this.log(dadobin);
var dado = new ASN1(dadobin);
assert(dado.tag == 0x7C);
assert(dado.elements == 1);
var nonceDO = dado.get(0);
assert((nonceDO.tag == 0x81) || (nonceDO.tag == 0x83));
var sectorId = nonceDO.value;
this.log("Sector specific identifier: " + sectorId);
return sectorId;
}
Documentation generated by
JSDoc on Tue Sep 3 22:29:38 2013