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  }