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 * Constructs an IssuerApplicationData instance by parsing a hexadecimal IAD string. 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 * Constructs an IssuerApplicationData instance by parsing a byte-array IAD. 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 * Returns a hexadecimal representation of the Derivation Key Index (DKI). 172 * @return A hexadecimal representation of the Derivation Key Index (DKI). 173 */ 174 public String getDerivationKeyIndex() { 175 return dki; 176 } 177 178 /** 179 * Returns a hexadecimal representation of the Cryptogram Version Number (CVN). 180 * @return A hexadecimal representation of the Cryptogram Version Number (CVN). 181 */ 182 public String getCryptogramVersionNumber() { 183 return cvn; 184 } 185 186 /** 187 * Returns a hexadecimal representation of the Common Core Identifier (CCI). 188 * @return A hexadecimal representation of the Common Core Identifier (CCI) 189 */ 190 public String getCommonCoreIdentifier() { 191 return cci; 192 } 193 194 /** 195 * Returns a hexadecimal representation of the Card Verification Results (CVR). 196 * @return A hexadecimal representation of the Card Verification Results (CVR). 197 */ 198 public String getCardVerificationResults() { 199 return cvr; 200 } 201 202 /** 203 * Returns a hexadecimal representation of the DAC/ICC dynamic number. 204 * @return A hexadecimal representation of the DAC/ICC dynamic number. 205 */ 206 public String getDAC() { 207 return dac; 208 } 209 210 /** 211 * Returns a hexadecimal representation of multiple counters, depending on the format. 212 * @return A hexadecimal representation of multiple counters, depending on the format. 213 */ 214 public String getCounters() { 215 return counters; 216 } 217 218 /** 219 * Returns the format of the IAD. 220 * @return The format of the IAD. 221 */ 222 public Format getFormat() { 223 return format; 224 } 225 226 /** 227 * Returns the Issuer Discretionary Data (IDD). 228 * @return The Issuer Discretionary Data (IDD). 229 */ 230 public String getIssuerDiscretionaryData() { 231 return idd; 232 } 233 234 /** 235 * Returns the last online Application Transaction Counter (ATC). 236 * @return hexadecimal representation of the last online ATC, or {@code null} if not present. 237 */ 238 public String getLastOnlineATC() { 239 return lastOnlineATC; 240 } 241 242 /** 243 * Returns the {@link CryptogramSpec} for this IAD format. 244 * @return the cryptogram specification 245 */ 246 public CryptogramSpec getCryptogramSpec() { 247 return Objects.requireNonNull(cryptogramSpec, "CryptogramSpec not implemented"); 248 } 249 @Override 250 public String toString() { 251 return iad; 252 } 253 254 @Override 255 public void dump(PrintStream p, String indent) { 256 257 String inner = indent + " "; 258 259 p.printf("%s<iad format='%s' value='%s'>", indent, getFormat().toString(), iad); 260 261 if (cci != null) 262 p.printf("%n%sCommon core identifier: '%s'", inner, getCommonCoreIdentifier()); 263 264 if (dki != null) 265 p.printf("%n%sKey derivation index: '%s'", inner, getDerivationKeyIndex()); 266 267 if (cvn != null) 268 p.printf("%n%sCryptogram version number: '%s'", inner, getCryptogramVersionNumber()); 269 270 if (cvr != null) 271 p.printf("%n%sCard verification results: '%s'", inner, getCardVerificationResults()); 272 273 if (idd != null) 274 p.printf("%n%sIssuer discretionary data: '%s'", inner, getIssuerDiscretionaryData()); 275 276 if (dac != null) 277 p.printf("%n%sDAC/ICC dynamic number: '%s'", inner, getDAC()); 278 279 if (counters != null) 280 p.printf("%n%sPlaintext/Encrypted counters: '%s'", inner, getCounters()); 281 if (lastOnlineATC != null) 282 p.printf("%n%sLast ATC Online: '%s'", inner, getLastOnlineATC()); 283 p.printf("%n%s</iad>%n", indent); 284 } 285 286 /** IAD format discriminator. */ 287 public enum Format { 288 /** Format could not be determined. */ 289 UNKNOWN, 290 /** Proprietary or unrecognised format. */ 291 OTHER, 292 /** Mastercard M/Chip format. */ 293 M_CHIP, 294 /** EMV Book 3 Format Code A. */ 295 EMV_FORMAT_A, 296 /** Visa VIS format. */ 297 VIS 298 } 299}