001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.emv; 020 021import org.jpos.emv.cryptogram.MChipCryptogram; 022import org.jpos.emv.cryptogram.CryptogramSpec; 023import org.jpos.emv.cryptogram.VISACryptogram; 024import org.jpos.iso.ISOUtil; 025import org.jpos.util.Loggeable; 026 027import java.io.PrintStream; 028import java.util.Arrays; 029import java.util.Objects; 030 031/** 032 * Issuer Application Data parser (IAD, tag 0x9F10) with support for the following formats: 033 * 034 * - VIS 1.5 035 * - M/CHIP 4 036 * - EMV Format A (as per the EMV 4.3 Book 3 spec) 037 */ 038public final class IssuerApplicationData implements Loggeable { 039 040 private final String iad; 041 private String cvn; 042 private String cvr; 043 private String dki; 044 private String idd; 045 private String dac; 046 private String counters; 047 private String lastOnlineATC; 048 private Format format; 049 private String cci; 050 051 private CryptogramSpec cryptogramSpec = null; 052 /** 053 * 054 * @param hexIAD Hexadecimal (String) representation of the IAD. 055 */ 056 public IssuerApplicationData(String hexIAD) { 057 058 Objects.requireNonNull(hexIAD, "IAD data cannot be null."); 059 060 iad = hexIAD.trim(); 061 062 if (iad.length() < 14) 063 throw new IllegalArgumentException("Invalid IAD length."); 064 065 format = Format.UNKNOWN; 066 067 int len = iad.length() / 2; // length in bytes 068 069 if (Arrays.asList(18, 20, 26, 28).contains(len)) { 070 //Therefore, the length of the Issuer Application Data is 18 bytes and for 071 // M/Chip Advance it may be 18, 20, 26, or 28. 072 unpackMCHIP(iad); 073 } else if (len == 32 && iad.startsWith("0F") && iad.startsWith("0F", 32)) { 074 // EMV_v4.3_Book_3 075 // C7.2 Issuer Application Data for Format Code 'A' 076 unpackEMVFormatA(iad); 077 } else if ((len >= 7 && len <= 23) || len == 32) { 078 // IAD Format 0/1/3 or IAD Format 2 079 unpackVIS(iad); 080 } else { 081 unpackOther(iad); 082 } 083 } 084 085 /** 086 * 087 * @param iad Byte array representation of the IAD. 088 */ 089 public IssuerApplicationData(byte[] iad) { 090 091 this(ISOUtil.byte2hex(Objects.requireNonNull(iad, "IAD data cannot be null."))); 092 } 093 094 private void unpackMCHIP(String data) { 095 096 int length = data.length(); 097 098 format = Format.M_CHIP; 099 dki = data.substring(0, 2); 100 cvn = data.substring(2, 4); 101 cryptogramSpec = new MChipCryptogram(cvn); 102 cvr = data.substring(4, 16); 103 dac = data.substring(16, 20); 104 105 //Counters (Plaintext or Encrypted)/Accumulators 106 if (length <= 40) { //20 bytes 107 counters = data.substring(20, 36); 108 } else { 109 counters = data.substring(20, 52); 110 } 111 112 // Last Online ATC (Only for M/Chip Advance) 113 if (length == 40) { 114 lastOnlineATC = data.substring(36, 40); 115 } else if (length == 56) { 116 lastOnlineATC = data.substring(52, 56); 117 } 118 } 119 120 private void unpackVIS(String iad) { 121 122 format = Format.VIS; 123 boolean format2 = iad.length() == 64; 124 125 if (format2) { 126 cvn = iad.substring(2, 4); 127 dki = iad.substring(4, 6); 128 cvr = iad.substring(6, 16); 129 idd = iad.substring(16); 130 } 131 else { 132 dki = iad.substring(2, 4); 133 cvn = iad.substring(4, 6); 134 cvr = iad.substring(6, 14); 135 136 if (iad.length() > 14) 137 idd = iad.substring(13); 138 } 139 cryptogramSpec = new VISACryptogram(cvn); 140 } 141 142 private void unpackEMVFormatA(String data) { 143 144 format = Format.EMV_FORMAT_A; 145 cci = data.substring(2, 4); 146 dki = data.substring(4, 6); 147 cvr = data.substring(6, 16); 148 idd = data.substring(34); 149 } 150 151 private void unpackOther(String data) { 152 153 format = Format.OTHER; 154 dki = data.substring(2, 4); 155 cvn = data.substring(4, 6); 156 157 if (data.length() == 64) { 158 String bridge = dki; 159 dki = cvn; 160 cvn = bridge; 161 } 162 163 int cvrLength = Integer.parseInt(data.substring(0, 2), 16); 164 cvr = data.substring(8, 8 + cvrLength); 165 166 if ((8 + 2 + cvrLength) < data.length()) 167 idd = data.substring(8 + 2 + cvrLength); 168 } 169 170 /** 171 * 172 * @return A hexadecimal representation of the Derivation Key Index (DKI). 173 */ 174 public String getDerivationKeyIndex() { 175 return dki; 176 } 177 178 /** 179 * 180 * @return A hexadecimal representation of the Cryptogram Version Number (CVN). 181 */ 182 public String getCryptogramVersionNumber() { 183 return cvn; 184 } 185 186 /** 187 * 188 * @return A hexadecimal representation of the Common Core Identifier (CCI) 189 */ 190 public String getCommonCoreIdentifier() { 191 return cci; 192 } 193 194 /** 195 * 196 * @return A hexadecimal representation of the Card Verification Results (CVR). 197 */ 198 public String getCardVerificationResults() { 199 return cvr; 200 } 201 202 /** 203 * 204 * @return A hexadecimal representation of the DAC/ICC dynamic number. 205 */ 206 public String getDAC() { 207 return dac; 208 } 209 210 /** 211 * 212 * @return A hexadecimal representation of multiple counters, depending on the format. 213 */ 214 public String getCounters() { 215 return counters; 216 } 217 218 /** 219 * 220 * @return The format of the IAD. 221 */ 222 public Format getFormat() { 223 return format; 224 } 225 226 /** 227 * 228 * @return The Isser Discretionary Data (IDD). 229 */ 230 public String getIssuerDiscretionaryData() { 231 return idd; 232 } 233 234 public String getLastOnlineATC() { 235 return lastOnlineATC; 236 } 237 238 public CryptogramSpec getCryptogramSpec() { 239 return Objects.requireNonNull(cryptogramSpec, "CryptogramSpec not implemented"); 240 } 241 @Override 242 public String toString() { 243 return iad; 244 } 245 246 @Override 247 public void dump(PrintStream p, String indent) { 248 249 String inner = indent + " "; 250 251 p.printf("%s<iad format='%s' value='%s'>", indent, getFormat().toString(), iad); 252 253 if (cci != null) 254 p.printf("%n%sCommon core identifier: '%s'", inner, getCommonCoreIdentifier()); 255 256 if (dki != null) 257 p.printf("%n%sKey derivation index: '%s'", inner, getDerivationKeyIndex()); 258 259 if (cvn != null) 260 p.printf("%n%sCryptogram version number: '%s'", inner, getCryptogramVersionNumber()); 261 262 if (cvr != null) 263 p.printf("%n%sCard verification results: '%s'", inner, getCardVerificationResults()); 264 265 if (idd != null) 266 p.printf("%n%sIssuer discretionary data: '%s'", inner, getIssuerDiscretionaryData()); 267 268 if (dac != null) 269 p.printf("%n%sDAC/ICC dynamic number: '%s'", inner, getDAC()); 270 271 if (counters != null) 272 p.printf("%n%sPlaintext/Encrypted counters: '%s'", inner, getCounters()); 273 if (lastOnlineATC != null) 274 p.printf("%n%sLast ATC Online: '%s'", inner, getLastOnlineATC()); 275 p.printf("%n%s</iad>%n", indent); 276 } 277 278 public enum Format { 279 UNKNOWN, 280 OTHER, 281 M_CHIP, 282 EMV_FORMAT_A, 283 VIS 284 } 285}