github.com/aldelo/common@v1.5.1/helper-emv.go (about) 1 package helper 2 3 import ( 4 "fmt" 5 "strings" 6 ) 7 8 /* 9 * Copyright 2020-2023 Aldelo, LP 10 * 11 * Licensed under the Apache License, Version 2.0 (the "License"); 12 * you may not use this file except in compliance with the License. 13 * You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, software 18 * distributed under the License is distributed on an "AS IS" BASIS, 19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 * See the License for the specific language governing permissions and 21 * limitations under the License. 22 */ 23 24 type EmvTlvTag struct { 25 TagName string 26 TagHexValueCount int 27 TagHexValue string 28 TagDecodedValue string 29 } 30 31 // getEmvTags returns list of emv tags used by this helper, 32 // future updates may add to this emv tag list 33 func getEmvTags() []string { 34 return []string{ 35 "4F", "50", "56", "57", "5A", "82", "84", "95", "9B", "9C", 36 "5F24", "5F25", "5F2D", "5F30", "5F34", "5F20", 37 "9F07", "9F08", "9F09", "9F11", "9F12", "9F0D", "9F0E", "9F0F", 38 "9F10", "9F1A", "9F26", "9F27", "9F33", "9F34", "9F35", "9F36", "9F37", "9F39", "9F40", 39 "DF78", "DF79", 40 } 41 } 42 43 // ParseEmvTlvTags accepts a hex payload of emv tlv data string, 44 // performs parsing of emv tags (2 and 4 digit hex as found in getEmvTags()), 45 // the expected emvTlvTagsPayload is tag hex + tag value len in hex + tag value in hex, data is composed without any other delimiters 46 // 47 // Reference Info: 48 // 49 // EMVLab Emv Tag Search = http://www.emvlab.org/emvtags/ 50 // EMVLab Emv Tags Decode Sample = http://www.emvlab.org/tlvutils/?data=6F2F840E325041592E5359532E4444463031A51DBF0C1A61184F07A0000000031010500A564953412044454249548701019000 51 // Hex To String Decoder = http://www.convertstring.com/EncodeDecode/HexDecode 52 // --- 53 // Stack Overflow Article = https://stackoverflow.com/questions/36740699/decode-emv-tlv-data 54 // Stack Overflow Article = https://stackoverflow.com/questions/15059580/reading-emv-card-using-ppse-and-not-pse/19593841#19593841 55 func ParseEmvTlvTags(emvTlvTagsPayload string) (foundList []*EmvTlvTag, err error) { 56 // validate 57 emvTlvTagsPayload, _ = ExtractAlphaNumeric(Replace(emvTlvTagsPayload, " ", "")) 58 emvTlvTagsPayload = strings.ToUpper(emvTlvTagsPayload) 59 60 if LenTrim(emvTlvTagsPayload) < 6 { 61 return nil, fmt.Errorf("EMV TLV Tags Payload Must Be 6 Digits or More") 62 } 63 64 if len(emvTlvTagsPayload)%2 != 0 { 65 return nil, fmt.Errorf("EMV TLV Tags Payload Must Be Formatted as Double HEX") 66 } 67 68 // get search tags 69 searchTags := getEmvTags() 70 71 if len(searchTags) == 0 { 72 return nil, fmt.Errorf("EMV Tags To Search is Required") 73 } 74 75 // store emv tags already processed 76 var processedTags []string 77 78 // loop until all emv tlv tags payload are processed 79 for len(emvTlvTagsPayload) >= 6 { 80 // get left 2 char, mid 2 char, and left 4 char, from left to match against emv search tags 81 left2 := Left(emvTlvTagsPayload, 2) 82 buf, e := HexToString(Mid(emvTlvTagsPayload, 2, 2)) 83 if e != nil { 84 return nil, e 85 } 86 left2HexValueCount, _ := ParseInt32(buf) 87 if left2HexValueCount < 0 { 88 left2HexValueCount = 0 89 } 90 91 mid2 := Mid(emvTlvTagsPayload, 2, 2) 92 buf, e = HexToString(Mid(emvTlvTagsPayload, 4, 2)) 93 if e != nil { 94 return nil, e 95 } 96 mid2HexValueCount, _ := ParseInt32(buf) 97 if mid2HexValueCount < 0 { 98 mid2HexValueCount = 0 99 } 100 101 left4 := Left(emvTlvTagsPayload, 4) 102 buf, e = HexToString(Mid(emvTlvTagsPayload, 4, 2)) 103 if e != nil { 104 return nil, e 105 } 106 left4HexValueCount, _ := ParseInt32(buf) 107 if left4HexValueCount < 0 { 108 left4HexValueCount = 0 109 } 110 111 checkMid4 := false 112 mid4 := "" 113 mid4HexvalueCount := 0 114 115 if len(emvTlvTagsPayload) >= 8 { 116 mid4 = Mid(emvTlvTagsPayload, 2, 4) 117 buf, e = HexToString(Mid(emvTlvTagsPayload, 6, 2)) 118 if e != nil { 119 return nil, e 120 } 121 mid4HexvalueCount, _ = ParseInt32(buf) 122 if mid4HexvalueCount < 0 { 123 mid4HexvalueCount = 0 124 } 125 checkMid4 = true 126 } 127 128 // loop through tags to search 129 matchFound := false 130 131 for _, t := range searchTags { 132 if LenTrim(t) > 0 && !StringSliceContains(&processedTags, t) && (len(t) == 2 || len(t) == 4) { 133 tagLenRemove := 0 134 tagValLen := 0 135 tagValHex := "" 136 tagValDecoded := "" 137 138 if len(t) == 2 { 139 // 2 140 if strings.ToUpper(left2) == strings.ToUpper(t) && left2HexValueCount > 0 { 141 tagLenRemove = 4 142 tagValLen = left2HexValueCount 143 } else if strings.ToUpper(mid2) == strings.ToUpper(t) && mid2HexValueCount > 0 { 144 tagLenRemove = 6 145 tagValLen = mid2HexValueCount 146 } 147 } else { 148 // 4 149 if strings.ToUpper(left4) == strings.ToUpper(t) && left4HexValueCount > 0 { 150 tagLenRemove = 6 151 tagValLen = left4HexValueCount 152 } else if checkMid4 && len(mid4) > 0 && strings.ToUpper(mid4) == strings.ToUpper(t) && mid4HexvalueCount > 0 { 153 tagLenRemove = 8 154 tagValLen = mid4HexvalueCount 155 } 156 } 157 158 if tagLenRemove > 0 && tagValLen > 0 { 159 // remove left x (tag and size) 160 emvTlvTagsPayload = Right(emvTlvTagsPayload, len(emvTlvTagsPayload)-tagLenRemove) 161 162 // get tag value hex 163 tagValHex = Left(emvTlvTagsPayload, tagValLen*2) 164 165 if tagValDecoded, err = HexToString(tagValHex); err != nil { 166 return nil, err 167 } 168 169 // remove tag value from payload 170 emvTlvTagsPayload = Right(emvTlvTagsPayload, len(emvTlvTagsPayload)-tagValLen*2) 171 172 // matched, finalize tag found 173 matchFound = true 174 175 foundList = append(foundList, &EmvTlvTag{ 176 TagName: t, 177 TagHexValueCount: tagValLen, 178 TagHexValue: tagValHex, 179 TagDecodedValue: tagValDecoded, 180 }) 181 182 processedTags = append(processedTags, t) 183 } 184 } 185 } 186 187 // after searching left most 2 char, and 4 char, if still cannot find a match for a corresponding hex, 188 // then the first 2 char need to be skipped (need to remove first 2 char of payload) 189 if !matchFound { 190 emvTlvTagsPayload = Right(emvTlvTagsPayload, len(emvTlvTagsPayload)-2) 191 } 192 } 193 194 // parsing completed 195 return foundList, nil 196 } 197 198 // ParseEmvTlvTagNamesOnly accepts a hex payload of emv tlv names string, 199 // performs parsing of emv tags (2 and 4 digit hex as found in getEmvTags()), 200 // the expected emvTlvTagsPayload is tag hex names appended one after another, without delimiters, no other tag values in the string 201 // 202 // Reference Info: 203 // 204 // EMVLab Emv Tag Search = http://www.emvlab.org/emvtags/ 205 // EMVLab Emv Tags Decode Sample = http://www.emvlab.org/tlvutils/?data=6F2F840E325041592E5359532E4444463031A51DBF0C1A61184F07A0000000031010500A564953412044454249548701019000 206 // Hex To String Decoder = http://www.convertstring.com/EncodeDecode/HexDecode 207 // --- 208 // Stack Overflow Article = https://stackoverflow.com/questions/36740699/decode-emv-tlv-data 209 // Stack Overflow Article = https://stackoverflow.com/questions/15059580/reading-emv-card-using-ppse-and-not-pse/19593841#19593841 210 func ParseEmvTlvTagNamesOnly(emvTlvTagNamesPayload string) (foundList []string, err error) { 211 // validate 212 emvTlvTagNamesPayload, _ = ExtractAlphaNumeric(Replace(emvTlvTagNamesPayload, " ", "")) 213 emvTlvTagNamesPayload = strings.ToUpper(emvTlvTagNamesPayload) 214 215 if LenTrim(emvTlvTagNamesPayload) < 2 { 216 return nil, fmt.Errorf("EMV TLV Tags Payload Must Be 2 Digits or More") 217 } 218 219 if len(emvTlvTagNamesPayload)%2 != 0 { 220 return nil, fmt.Errorf("EMV TLV Tags Payload Must Be Formatted as Double HEX") 221 } 222 223 // get search tags 224 searchTags := getEmvTags() 225 226 if len(searchTags) == 0 { 227 return nil, fmt.Errorf("EMV Tags To Search is Required") 228 } 229 230 // loop until all emv tlv tags payload are processed 231 for len(emvTlvTagNamesPayload) >= 2 { 232 // get left 2 char, and left 4 char, from left to match against emv search tags 233 left2 := Left(emvTlvTagNamesPayload, 2) 234 235 if StringSliceContains(&searchTags, left2) { 236 // left 2 match 237 foundList = append(foundList, left2) 238 emvTlvTagNamesPayload = Right(emvTlvTagNamesPayload, len(emvTlvTagNamesPayload)-2) 239 continue 240 } 241 242 if len(emvTlvTagNamesPayload) >= 4 { 243 left4 := Left(emvTlvTagNamesPayload, 4) 244 245 if StringSliceContains(&searchTags, left4) { 246 // left 4 match 247 foundList = append(foundList, left4) 248 emvTlvTagNamesPayload = Right(emvTlvTagNamesPayload, len(emvTlvTagNamesPayload)-4) 249 continue 250 } 251 } 252 253 // left 2 and 4 no match, remove first 2 char 254 emvTlvTagNamesPayload = Right(emvTlvTagNamesPayload, len(emvTlvTagNamesPayload)-2) 255 } 256 257 // parsing completed 258 return foundList, nil 259 } 260 261 // cn = compressed numeric data element, consists of 2 numeric digits in hex 0 - 9, 262 // 263 // left justified, padded with trailing F 264 // 265 // --- 266 // DFA001 = PAN key entered (cn) 267 // DFA002 = CVV/CID (cn) 268 // DFA003 = Expiry Date (YYMM) (cn) 269 // DFA004 = Raw MSR Track 2 with Start and End Sentinel (ascii) 270 // DFA005 = Raw MSR Track 1 with Start and End Sentinel (ascii) 271 // 57 = Track 2 Equivalent Data 272 // 5A = PAN (cn) 273 // 9F6B = Track 2 Data 274 // 56 = Track 1 Data 275 // 9F1F = Track 1 Discretionary Data 276 // 9F20 = Track 2 Discretionary Data 277 func getEncryptedTlvTags() []string { 278 return []string{ 279 "DFA001", "DFA002", "DFA003", "DFA004", "DFA005", 280 "57", "5A", "9F6B", "56", "9F1F", "9F20", 281 } 282 } 283 284 func getEncryptedTlvTagsAscii() []string { 285 return []string{ 286 "DFA004", "DFA005", 287 } 288 } 289 290 // ParseEncryptedTlvTags accepts a hex payload of encrypted tlv data string, 291 // performs parsing of emv tags (2, 4 and 6 digit hex as found in getEncryptedTlvTags()), 292 // the expected encryptedTlvTagsPayload is tag hex + tag value len in hex + tag value in hex, data is composed without any other delimiters 293 // 294 // Reference Info: 295 // 296 // EMVLab Emv Tag Search = http://www.emvlab.org/emvtags/ 297 // EMVLab Emv Tags Decode Sample = http://www.emvlab.org/tlvutils/?data=6F2F840E325041592E5359532E4444463031A51DBF0C1A61184F07A0000000031010500A564953412044454249548701019000 298 // Hex To String Decoder = http://www.convertstring.com/EncodeDecode/HexDecode 299 // --- 300 // Stack Overflow Article = https://stackoverflow.com/questions/36740699/decode-emv-tlv-data 301 // Stack Overflow Article = https://stackoverflow.com/questions/15059580/reading-emv-card-using-ppse-and-not-pse/19593841#19593841 302 func ParseEncryptedTlvTags(encryptedTlvTagsPayload string) (foundList []*EmvTlvTag, err error) { 303 // validate 304 if LenTrim(encryptedTlvTagsPayload) < 6 { 305 return nil, fmt.Errorf("Encrypted TLV Tags Payload Must Be 6 Digits or More") 306 } 307 308 // get search tags 309 searchTags := getEncryptedTlvTags() 310 311 if len(searchTags) == 0 { 312 return nil, fmt.Errorf("Encrypted TLV Tags To Search is Required") 313 } 314 315 asciiTags := getEncryptedTlvTagsAscii() 316 317 // store emv tags already processed 318 var processedTags []string 319 320 // loop until all tlv tags payload are processed 321 for len(encryptedTlvTagsPayload) >= 6 { 322 // get left 2 char, mid 2 char, and left 4 char, from left to match against emv search tags 323 // get left 6 char (for DF tags) 324 left2 := Left(encryptedTlvTagsPayload, 2) 325 buf, e := HexToString(Mid(encryptedTlvTagsPayload, 2, 2)) 326 if e != nil { 327 return nil, e 328 } 329 left2HexValueCount, _ := ParseInt32(buf) 330 if left2HexValueCount < 0 { 331 left2HexValueCount = 0 332 } 333 334 mid2 := Mid(encryptedTlvTagsPayload, 2, 2) 335 buf, e = HexToString(Mid(encryptedTlvTagsPayload, 4, 2)) 336 if e != nil { 337 return nil, e 338 } 339 mid2HexValueCount, _ := ParseInt32(buf) 340 if mid2HexValueCount < 0 { 341 mid2HexValueCount = 0 342 } 343 344 left4 := Left(encryptedTlvTagsPayload, 4) 345 buf, e = HexToString(Mid(encryptedTlvTagsPayload, 4, 2)) 346 if e != nil { 347 return nil, e 348 } 349 left4HexValueCount, _ := ParseInt32(buf) 350 if left4HexValueCount < 0 { 351 left4HexValueCount = 0 352 } 353 354 checkMid4 := false 355 mid4 := "" 356 mid4HexValueCount := 0 357 358 if len(encryptedTlvTagsPayload) >= 8 { 359 mid4 = Mid(encryptedTlvTagsPayload, 2, 4) 360 buf, e = HexToString(Mid(encryptedTlvTagsPayload, 6, 2)) 361 if e != nil { 362 return nil, e 363 } 364 mid4HexValueCount, _ = ParseInt32(buf) 365 if mid4HexValueCount < 0 { 366 mid4HexValueCount = 0 367 } 368 checkMid4 = true 369 } 370 371 checkLeft6 := false 372 left6 := "" 373 left6HexValueCount := 0 374 375 if len(encryptedTlvTagsPayload) >= 8 { 376 left6 = Left(encryptedTlvTagsPayload, 6) 377 buf, e = HexToString(Mid(encryptedTlvTagsPayload, 6, 2)) 378 if e != nil { 379 return nil, e 380 } 381 left6HexValueCount, _ = ParseInt32(buf) 382 if left6HexValueCount < 0 { 383 left6HexValueCount = 0 384 } 385 checkLeft6 = true 386 } 387 388 // loop through tags to search 389 matchFound := false 390 391 for _, t := range searchTags { 392 if LenTrim(t) > 0 && !StringSliceContains(&processedTags, t) && (len(t) == 2 || len(t) == 4 || len(t) == 6) { 393 tagLenRemove := 0 394 tagValLen := 0 395 tagValHex := "" 396 tagValDecoded := "" 397 398 if len(t) == 2 { 399 // 2 400 if strings.ToUpper(left2) == strings.ToUpper(t) && left2HexValueCount > 0 { 401 tagLenRemove = 4 402 tagValLen = left2HexValueCount 403 } else if strings.ToUpper(mid2) == strings.ToUpper(t) && mid2HexValueCount > 0 { 404 tagLenRemove = 6 405 tagValLen = mid2HexValueCount 406 } 407 } else if len(t) == 4 { 408 // 4 409 if strings.ToUpper(left4) == strings.ToUpper(t) && left4HexValueCount > 0 { 410 tagLenRemove = 6 411 tagValLen = left4HexValueCount 412 } else if checkMid4 && len(mid4) > 0 && strings.ToUpper(mid4) == strings.ToUpper(t) && mid4HexValueCount > 0 { 413 tagLenRemove = 8 414 tagValLen = mid4HexValueCount 415 } 416 } else if checkLeft6 { 417 // 6 418 if strings.ToUpper(left6) == strings.ToUpper(t) && left6HexValueCount > 0 { 419 tagLenRemove = 8 420 tagValLen = left6HexValueCount 421 } 422 } 423 424 if tagLenRemove > 0 && tagValLen > 0 { 425 // remove left x (tag and size) 426 encryptedTlvTagsPayload = Right(encryptedTlvTagsPayload, len(encryptedTlvTagsPayload)-tagLenRemove) 427 428 // get tag value hex 429 if !StringSliceContains(&asciiTags, t) { 430 // hex 431 tagValHex = Left(encryptedTlvTagsPayload, tagValLen*2) 432 433 if tagValDecoded, err = HexToString(tagValHex); err != nil { 434 return nil, err 435 } 436 437 // remove tag value from payload 438 encryptedTlvTagsPayload = Right(encryptedTlvTagsPayload, len(encryptedTlvTagsPayload)-tagValLen*2) 439 } else { 440 // ascii 441 tagValHex = Left(encryptedTlvTagsPayload, tagValLen) 442 tagValDecoded = tagValHex 443 444 // remove tag value from payload 445 encryptedTlvTagsPayload = Right(encryptedTlvTagsPayload, len(encryptedTlvTagsPayload)-tagValLen) 446 } 447 448 // matched, finalize tag found 449 matchFound = true 450 451 foundList = append(foundList, &EmvTlvTag{ 452 TagName: t, 453 TagHexValueCount: tagValLen, 454 TagHexValue: tagValHex, 455 TagDecodedValue: tagValDecoded, 456 }) 457 458 processedTags = append(processedTags, t) 459 } 460 } 461 } 462 463 // after searching left most 2 char, 4 char, 6 char, if still cannot find a match for a corresponding hex, 464 // then the first 2 char need to be skipped (need to remove first 2 char of payload) 465 if !matchFound { 466 encryptedTlvTagsPayload = Right(encryptedTlvTagsPayload, len(encryptedTlvTagsPayload)-2) 467 } 468 } 469 470 // parsing completed 471 return foundList, nil 472 }